feedback5: merge W5E — a full hand refuses buys and reward picks, temp pieces fill freed slots (#82); 联防 plays on the official escaped maps (#41)
# Conflicts: # test/golden/fields.json # test/golden/matches.json
This commit is contained in:
commit
ece75b6853
37 files changed
+732
-265
No files matched your search
+1
-1
@@ -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
@@ -411,12 +411,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`
|
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).
|
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 |
|
| 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 |
|
| `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) |
|
| `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]` | |
|
| `size` | `[19,21]` | |
|
||||||
| `rows[]` | `rows[9] = "##Errr#rrrSrrr#rrrS##"` | 19 strings, **index = row (0 = bottom)**, one glyph per col |
|
| `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) |
|
| `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) |
|
||||||
|
|||||||
+2
-2
@@ -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).
|
- 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).
|
- **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).
|
- **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).
|
- **Speed:** `tilesPerSecond = moveSpeed × MOVE_SCALE` with `MOVE_SCALE = 0.5` (in `sim/constants.js`, tunable).
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -386,7 +386,7 @@ Summons (§20.1): an operator on the board sends its manually deployable summons
|
|||||||
|
|
||||||
Equipment (research 04 §2 + addendum, 09 §1.2): 2 slots per chess; a third item on a full carrier replaces the equipped item `replaceUid` names (the replace dialog's pick, official `UseEquipUp.unloadInstId`; absent ⇒ the oldest; a uid not equipped on the target ⇒ `BAD_TARGET`, nothing changes) and the replaced item is destroyed. Equipped items are otherwise **locked**: `destroy` (销毁) accepts hand / temp items only (`BAD_TARGET 'equipped items are locked'`); equipment goes back to the hand only when its carrier merges, is sold, or is destroyed by 突变细胞's transformation (the cell itself returns too; PRTS 帮助 "在失去该干员(干员出售、销毁、合并等)…时自动卸除", §21.1) — a sale is refused with `HAND_FULL` when there is no room for it; returned equipment is stowed before the operator a transformation gains and before a merge elite's summon cards, and every path that hands the player an item — returned equipment included — ends with the auto-merge, so no player holds two identical mergeable normal items (§21.7).
|
Equipment (research 04 §2 + addendum, 09 §1.2): 2 slots per chess; a third item on a full carrier replaces the equipped item `replaceUid` names (the replace dialog's pick, official `UseEquipUp.unloadInstId`; absent ⇒ the oldest; a uid not equipped on the target ⇒ `BAD_TARGET`, nothing changes) and the replaced item is destroyed. Equipped items are otherwise **locked**: `destroy` (销毁) accepts hand / temp items only (`BAD_TARGET 'equipped items are locked'`); equipment goes back to the hand only when its carrier merges, is sold, or is destroyed by 突变细胞's transformation (the cell itself returns too; PRTS 帮助 "在失去该干员(干员出售、销毁、合并等)…时自动卸除", §21.1) — a sale is refused with `HAND_FULL` when there is no room for it; returned equipment is stowed before the operator a transformation gains and before a merge elite's summon cards, and every path that hands the player an item — returned equipment included — ends with the auto-merge, so no player holds two identical mergeable normal items (§21.7).
|
||||||
|
|
||||||
Economy/shop/pool/merge/hand rules: exactly as research 00-INDEX §3–§4 (income `min(3+r,12)`; prices by tier; refresh 1; freeze toggles all; sell +1; items not sellable; upgrade prices per mode with −1/round; shared pool copies 12/14/18/16/8/5 (缪尔赛思 4) across all players; elite = 3 copies; odds = copy-weighted draw over unbanned visible chess with tier ≤ level; merge 3 (风丸 2) → elite onto the consumed deployed copy's tile, else to the hand (§20.11) + reward offer of 3 **different** free chess of tier `min(level+1, 6)` (copy-weighted; a tier short of different chess tops up from the tier below [ASSUMED], §20.6); purchase completing a merge allowed with full hand; full hand blocks buy; overflow to temp; temp blocks ready).
|
Economy/shop/pool/merge/hand rules: exactly as research 00-INDEX §3–§4 (income `min(3+r,12)`; prices by tier; refresh 1; freeze toggles all; sell +1; items not sellable; upgrade prices per mode with −1/round; shared pool copies 12/14/18/16/8/5 (缪尔赛思 4) across all players; elite = 3 copies; odds = copy-weighted draw over unbanned visible chess with tier ≤ level; merge 3 (风丸 2) → elite onto the consumed deployed copy's tile, else to the hand (§20.11) + reward offer of 3 **different** free chess of tier `min(level+1, 6)` (copy-weighted; a tier short of different chess tops up from the tier below [ASSUMED], §20.6); full hand blocks every buy / reward pick, a merge-completing one too (PRTS 卫戍协议/帮助 §手牌区, GitHub #82); overflow to temp; a free hand slot pulls the temp pieces back in at once, right → left (`PlayerState._fillHandFromTemp`); temp blocks ready).
|
||||||
|
|
||||||
**Temp overflow (临时整备区, row 8; user playtest #3 item 3).** Passive gains that find the hand full (merge elites, grants — the operator a 突变细胞 transformation gains included, §21.1 —, battle-result rewards, equipment returned by a merged / sold / transformed carrier) overflow into the 5 temp slots, and Ready is refused while any is occupied (official "直到溢出情况排除才可开始进行作战"). A temp piece is resolved — a chess sold back to the pool with its summons removed, items destroyed, a summon stack removed (it comes back at the next round start, PRTS "干员所属召唤物会于下一回合返还", §20.1) — only at the deadline of the **first prep in which its player could act on it** (`PlayerState.tempDue(piece)` vs `prepsEnded`; every write into a temp slot goes through `_putTemp`, which records it): a piece that arrived during a prep before Ready is due at that prep's end; one that arrived after Ready or during the `<休整期结束时>` effects, or outside PREP (COMBAT, 联防, SETTLE — battle-result grants, a SETTLE merge's elite, returned equipment —, the next ROUND_START, 机变) is kept, visible and usable (move to a free hand slot, place, equip, use, destroy, sell) through the **next** prep. Un-readying (`setReady(false)`) makes whatever arrived while ready due at the current prep. ROUND_START no longer wipes temp (it used to destroy what SETTLE had put there before the player ever saw it). A merge's elite whose merge consumed a deployed copy takes that copy's tile and facing (§20.11); otherwise it goes to the hand, overflowing into temp (also outside PREP). `server/match/audit.js` (`tools/matchrun.mjs --check`) tracks the arrivals itself and flags any temp piece that outlives its due prep. The UI frames the row on the board with the rule (§17.5).
|
**Temp overflow (临时整备区, row 8; user playtest #3 item 3).** Passive gains that find the hand full (merge elites, grants — the operator a 突变细胞 transformation gains included, §21.1 —, battle-result rewards, equipment returned by a merged / sold / transformed carrier) overflow into the 5 temp slots, and Ready is refused while any is occupied (official "直到溢出情况排除才可开始进行作战"). A temp piece is resolved — a chess sold back to the pool with its summons removed, items destroyed, a summon stack removed (it comes back at the next round start, PRTS "干员所属召唤物会于下一回合返还", §20.1) — only at the deadline of the **first prep in which its player could act on it** (`PlayerState.tempDue(piece)` vs `prepsEnded`; every write into a temp slot goes through `_putTemp`, which records it): a piece that arrived during a prep before Ready is due at that prep's end; one that arrived after Ready or during the `<休整期结束时>` effects, or outside PREP (COMBAT, 联防, SETTLE — battle-result grants, a SETTLE merge's elite, returned equipment —, the next ROUND_START, 机变) is kept, visible and usable (move to a free hand slot, place, equip, use, destroy, sell) through the **next** prep. Un-readying (`setReady(false)`) makes whatever arrived while ready due at the current prep. ROUND_START no longer wipes temp (it used to destroy what SETTLE had put there before the player ever saw it). A merge's elite whose merge consumed a deployed copy takes that copy's tile and facing (§20.11); otherwise it goes to the hand, overflowing into temp (also outside PREP). `server/match/audit.js` (`tools/matchrun.mjs --check`) tracks the arrivals itself and flags any temp piece that outlives its due prep. The UI frames the row on the board with the rule (§17.5).
|
||||||
|
|
||||||
|
|||||||
+14
-6
@@ -407,8 +407,8 @@ Writes (all validated, never throw on bad input, never make funds / pools negati
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `addFunds(n, reason?)` / `addPendingFunds(n)` / `spendFunds(n)` | funds now / at the next round start / pay (false when short) |
|
| `addFunds(n, reason?)` / `addPendingFunds(n)` / `spendFunds(n)` | funds now / at the next round start / pay (false when short) |
|
||||||
| `addLayers(bondId, n, { requireActive })` | layer gain (`requireActive` = "使已激活的…"); returns layers added — at most the room left under `BOND_LAYER_CAP` (999, shared/constants.js `layerGainRoom`; the battle gains merged at SETTLE too); fires onLayers unless it added 0 |
|
| `addLayers(bondId, n, { requireActive })` | layer gain (`requireActive` = "使已激活的…"); returns layers added — at most the room left under `BOND_LAYER_CAP` (999, shared/constants.js `layerGainRoom`; the battle gains merged at SETTLE too); fires onLayers unless it added 0 |
|
||||||
| `grantChess(id, { toTemp, golden, requirePool=true, fromPool=true })` | acquire a chess (takes pool copies; with `requirePool` a pool chess with no copy left fails → `null`); merges; fires onGain |
|
| `grantChess(id, { toTemp, golden, requirePool=true, fromPool=true })` | acquire a chess (takes pool copies; with `requirePool` a pool chess with no copy left fails → `null`); merges; fires onGain. `toTemp` puts it into temp, but a free hand slot pulls it in at the next recompute (temp holds overflow only, `PlayerState._fillHandFromTemp`) |
|
||||||
| `grantItem(id, { toTemp })` | acquire an item (merges with an identical normal item) |
|
| `grantItem(id, { toTemp })` | acquire an item (merges with an identical normal item); `toTemp` as for grantChess |
|
||||||
| `rollChess({ maxTier, tier, bond, filter })` / `rollItem({ pool, tier, maxTier })` | copy-weighted chess id from the shared pool / item id (choices.json pools) |
|
| `rollChess({ maxTier, tier, bond, filter })` / `rollItem({ pool, tier, maxTier })` | copy-weighted chess id from the shared pool / item id (choices.json pools) |
|
||||||
| `grantFreeRefresh(n)` | free refreshes (stack) |
|
| `grantFreeRefresh(n)` | free refreshes (stack) |
|
||||||
| `modifyPrice(delta)` / `setPrice(v)` | onPrice only: edit `ev.price` |
|
| `modifyPrice(delta)` / `setPrice(v)` | onPrice only: edit `ev.price` |
|
||||||
@@ -508,9 +508,14 @@ Any `choice:` handler whose EffectRef reuses its own key must guard like this (o
|
|||||||
* **Pool**: copies 12/14/18/16/8/5 (缪尔赛思 4); a normal piece holds 1 copy, an elite 3; displays never reserve copies;
|
* **Pool**: copies 12/14/18/16/8/5 (缪尔赛思 4); a normal piece holds 1 copy, an elite 3; displays never reserve copies;
|
||||||
selling, temp resolution and elimination return exactly what a piece holds (`left + held = cap` always).
|
selling, temp resolution and elimination return exactly what a piece holds (`left + held = cap` always).
|
||||||
* **Hand**: 10 slots filled right→left, 5 temp slots for passive overflow (merge results, grants, returned equipment);
|
* **Hand**: 10 slots filled right→left, 5 temp slots for passive overflow (merge results, grants, returned equipment);
|
||||||
a full hand refuses buys unless the purchase completes a merge, and withdrawals unless the withdrawn summoner's own
|
a full hand refuses every buy and reward pick — also one whose copy would complete a merge at once (PRTS
|
||||||
summon stack frees a slot (a deployed summon withdrawn with no stack of its own left to join is a new card: `HAND_FULL`,
|
卫戍协议/帮助 §手牌区 "例如招募/购入等通常情况下会增加手牌的操作"; GitHub #82) — and withdrawals unless the withdrawn
|
||||||
never temp); temp blocks Ready. A temp piece is resolved (chess sold back to the pool, items destroyed, a summon stack
|
summoner's own summon stack frees a slot (a deployed summon withdrawn with no stack of its own left to join is a new
|
||||||
|
card: `HAND_FULL`, never temp). A free hand slot pulls the temp pieces in at once ("常规手牌区出现空位时自动移入"):
|
||||||
|
`PlayerState._fillHandFromTemp` at every recompute, the temp row right → left (the order it fills, [ASSUMED]) into the
|
||||||
|
hand's free slots right → left — after a sale, a deployment, a merge that consumed hand copies, an item equipped /
|
||||||
|
destroyed / used, a summon stack removed with its owner; so temp holds pieces only while the hand is full
|
||||||
|
(`invariants.js`). Temp blocks Ready. A temp piece is resolved (chess sold back to the pool, items destroyed, a summon stack
|
||||||
removed — it comes back at the next round start, see Summons) at the deadline of the first prep in which its player could act on it (`PlayerState.tempDue` vs
|
removed — it comes back at the next round start, see Summons) at the deadline of the first prep in which its player could act on it (`PlayerState.tempDue` vs
|
||||||
`prepsEnded`, recorded by `_putTemp`, user playtest #3): arrived during a prep before Ready → that prep's end; after
|
`prepsEnded`, recorded by `_putTemp`, user playtest #3): arrived during a prep before Ready → that prep's end; after
|
||||||
Ready, during onPrepEnd, or outside PREP (COMBAT, 联防, SETTLE, ROUND_START, 机变) → the end of the NEXT prep, so it
|
Ready, during onPrepEnd, or outside PREP (COMBAT, 联防, SETTLE, ROUND_START, 机变) → the end of the NEXT prep, so it
|
||||||
@@ -658,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
|
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
|
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
|
(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
|
`{ 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
|
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
|
combat carries `{ down: true }` (PRTS 卫戍协议/帮助: "部署完成后…上一阶段为退场状态的干员强制退场"): deployed, then forced out
|
||||||
|
|||||||
+2
-1
@@ -54,7 +54,7 @@
|
|||||||
|
|
||||||
- **购买点两次**:第一次点商店卡片只选中(卡片抬起并打开详情,底部出现「确认购买 · 再次点击」),再点这张卡片的**任意位置**(包括金色的确认条)才购买;点别处或按 Esc 取消。升级调度中心、晋升奖励同样两次;策略或装备带来的免费挑选(如凯瑟琳【定向投放】升级后刷新的 3 件装备、娜仁图亚【见者有份】的 2 件装备、寻呼模块的 3 名干员)也一样出现在商店卡片的位置,左侧标题写着它的来源,装备显示为装备卡(图标、名称、说明、FREE),选 1 个免费获得;同时有多个免费挑选时依次出现,标题下和「稍后」后的提示按钮会写还有几项(如「+1」)。不能购买时(例如已准备就绪)点卡片只打开详情。
|
- **购买点两次**:第一次点商店卡片只选中(卡片抬起并打开详情,底部出现「确认购买 · 再次点击」),再点这张卡片的**任意位置**(包括金色的确认条)才购买;点别处或按 Esc 取消。升级调度中心、晋升奖励同样两次;策略或装备带来的免费挑选(如凯瑟琳【定向投放】升级后刷新的 3 件装备、娜仁图亚【见者有份】的 2 件装备、寻呼模块的 3 名干员)也一样出现在商店卡片的位置,左侧标题写着它的来源,装备显示为装备卡(图标、名称、说明、FREE),选 1 个免费获得;同时有多个免费挑选时依次出现,标题下和「稍后」后的提示按钮会写还有几项(如「+1」)。不能购买时(例如已准备就绪)点卡片只打开详情。
|
||||||
- **公共卡池**(同盟):每名干员的副本数有限,I–VI 阶分别 12 / 14 / 18 / 16 / 8 / 5 份,所有玩家共用;持有的干员(棋盘 + 整备区)占用副本,商店里展示的不占用。一名干员被大家拿光后就刷不出来了;淘汰玩家的干员会还回卡池。
|
- **公共卡池**(同盟):每名干员的副本数有限,I–VI 阶分别 12 / 14 / 18 / 16 / 8 / 5 份,所有玩家共用;持有的干员(棋盘 + 整备区)占用副本,商店里展示的不占用。一名干员被大家拿光后就刷不出来了;淘汰玩家的干员会还回卡池。
|
||||||
- **位置上限**:棋盘最多部署 **8** 名;整备区 **10** 格;整备区满时新获得的单位(合成的精锐、奖励、退回的装备等)进入**临时整备区**(整备区前面的 5 格)。临时整备区有单位时**不能准备就绪**,棋盘上会用红色虚线框标出这一排并说明规则:把它们放进整备区或战场、配发或使用,或者出售 / 销毁。休整期中进入临时整备区的单位,若到**本休整期结束**时仍留在那里就会被自动处理(干员返还卡池、装备销毁);作战、结算期间或准备就绪之后才进入的(例如战斗奖励、结算时合成的精锐),会保留到**下一个休整期**结束,你一定有机会处理它们。
|
- **位置上限**:棋盘最多部署 **8** 名;整备区 **10** 格。整备区满时**不能购买或领取**任何干员、装备(包括能凑齐 3 名合成精锐的那一张,和官方一样),先出售、部署或配发腾出格子;新获得的单位(合成的精锐、奖励、退回的装备等)进入**临时整备区**(整备区前面的 5 格),整备区一空出格子,临时整备区的单位就**自动补进去**(从右往左)。临时整备区有单位时**不能准备就绪**,棋盘上会用红色虚线框标出这一排并说明规则:把它们放进整备区或战场、配发或使用,或者出售 / 销毁。休整期中进入临时整备区的单位,若到**本休整期结束**时仍留在那里就会被自动处理(干员返还卡池、装备销毁);作战、结算期间或准备就绪之后才进入的(例如战斗奖励、结算时合成的精锐),会保留到**下一个休整期**结束,你一定有机会处理它们。
|
||||||
- **晋升精锐**:3 名同名干员(无论是否部署)自动合成**精锐**(等级、技能、模组大幅提升;风丸只需 2 名),精锐不会再合成。如果被合成的干员里有**已经部署在战场上**的,精锐会直接出现在**那名干员的位置**(保持原来的朝向,不多占部署位;有两三名都在场上时,取最先部署的那名——最左一列优先,同一列从上到下);否则精锐进入整备区(满了进入临时整备区)。原先配发的装备都退回整备区,需要重新配发;被合成干员的召唤物随之移除,场上的精锐会把自己的召唤物送进整备区。点选一张能合成的商店卡片时,精锐将出现的那一格会亮起金色边框,同时打开的详情卡里也会写明去向(「可晋升:精锐干员将出现在作战区原位置 / 将进入整备区」;详情卡会自动停靠在不遮挡这一格的一侧)。在回合开始或结算时因策略、奖励等完成的合成,精锐出现时同样会播放金色的晋升特效。
|
- **晋升精锐**:3 名同名干员(无论是否部署)自动合成**精锐**(等级、技能、模组大幅提升;风丸只需 2 名),精锐不会再合成。如果被合成的干员里有**已经部署在战场上**的,精锐会直接出现在**那名干员的位置**(保持原来的朝向,不多占部署位;有两三名都在场上时,取最先部署的那名——最左一列优先,同一列从上到下);否则精锐进入整备区(满了进入临时整备区)。原先配发的装备都退回整备区,需要重新配发;被合成干员的召唤物随之移除,场上的精锐会把自己的召唤物送进整备区。点选一张能合成的商店卡片时,精锐将出现的那一格会亮起金色边框,同时打开的详情卡里也会写明去向(「可晋升:精锐干员将出现在作战区原位置 / 将进入整备区」;详情卡会自动停靠在不遮挡这一格的一侧)。在回合开始或结算时因策略、奖励等完成的合成,精锐出现时同样会播放金色的晋升特效。
|
||||||
- **晋级奖励**:每次晋升后获得一次特殊招募:从 3 名 **高一阶**(最高 VI)、**互不相同**的随机干员中**免费**挑 1 名(该阶剩余干员不足 3 名时由低一阶补足)。它不能刷新、不能冻结、本回合结束失效。
|
- **晋级奖励**:每次晋升后获得一次特殊招募:从 3 名 **高一阶**(最高 VI)、**互不相同**的随机干员中**免费**挑 1 名(该阶剩余干员不足 3 名时由低一阶补足)。它不能刷新、不能冻结、本回合结束失效。
|
||||||
- 每回合商店会自动刷新未冻结的格子。
|
- 每回合商店会自动刷新未冻结的格子。
|
||||||
@@ -83,6 +83,7 @@
|
|||||||
某回合**有人漏怪、又有人完美作战**(全歼且无漏怪)时触发:最多 2 名完美作战的队友带着自己的阵容(与官方相同,只保留作战结束时的生命比例和技力:作战结束时还开着的技能在联防开场是关着的,技力是开技能后剩下的值,通常为 0;召唤物只保留技力;作战结束时已被击倒的干员在原位倒地(阿戈尔的开场吞噬仍把它当作站着的干员计算标记与基础攻击力、阻挡数,之后它照常退场),按官方的「强制退场」以完整的再部署时间重新倒计时,时间到且部署费用足够后照常再部署——联防较短时可能来不及再部署;机变「征召」这类开战时生效的再部署时间减免同样计入),迎击其他玩家漏掉的全部敌人。盟约层数按本回合自己作战结束时达到的层数(即盟约栏显示的层数)计算,联防中不再叠加层数;这些层数在结算时只计入一次。
|
某回合**有人漏怪、又有人完美作战**(全歼且无漏怪)时触发:最多 2 名完美作战的队友带着自己的阵容(与官方相同,只保留作战结束时的生命比例和技力:作战结束时还开着的技能在联防开场是关着的,技力是开技能后剩下的值,通常为 0;召唤物只保留技力;作战结束时已被击倒的干员在原位倒地(阿戈尔的开场吞噬仍把它当作站着的干员计算标记与基础攻击力、阻挡数,之后它照常退场),按官方的「强制退场」以完整的再部署时间重新倒计时,时间到且部署费用足够后照常再部署——联防较短时可能来不及再部署;机变「征召」这类开战时生效的再部署时间减免同样计入),迎击其他玩家漏掉的全部敌人。盟约层数按本回合自己作战结束时达到的层数(即盟约栏显示的层数)计算,联防中不再叠加层数;这些层数在结算时只计入一次。
|
||||||
|
|
||||||
- 参加者按官方规则挑选:场上单位最多 > 有已激活的盟约 > 未倒下的单位最多 > 座位靠前(与 LP 无关);两人参加时,排在前面的一位(再比较激活盟约的层数总和)在右半场「率先迎敌」。
|
- 参加者按官方规则挑选:场上单位最多 > 有已激活的盟约 > 未倒下的单位最多 > 座位靠前(与 LP 无关);两人参加时,排在前面的一位(再比较激活盟约的层数总和)在右半场「率先迎敌」。
|
||||||
|
- 联防在官方的**联防专用地图**上进行(与官方相同,不是本回合的战场):一整片道路,没有水池、箱子、装置等地形;干员按休整期的位置站好。一人协防时敌人从中间的入口进入左半场;两人协防时敌人从最右侧进入,先经过右半场,再穿过中间进入左半场。
|
||||||
|
|
||||||
- 只有完美作战的队友能参与联防;联防中**盟约不会叠加层数**。
|
- 只有完美作战的队友能参与联防;联防中**盟约不会叠加层数**。
|
||||||
- 联防中击杀悬赏敌人,资金归击杀者。
|
- 联防中击杀悬赏敌人,资金归击杀者。
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ Boss rounds: the battle continues past the timer. Merged team LP drains **1/s af
|
|||||||
|
|
||||||
The visible chess per tier are 16/17/19/22/19/19. The item slot uses the same tier shares, then picks uniformly within the tier [ASSUMED].
|
The visible chess per tier are 16/17/19/22/19/19. The item slot uses the same tier shares, then picks uniformly within the tier [ASSUMED].
|
||||||
|
|
||||||
**Merge:** 3 copies of the same chess (board or hand) merge into 1 elite (`_b`), sent to the **hand** — or, when a consumed copy was deployed, to **that copy's board position** (PRTS 卫戍协议/帮助 "若消耗已部署至作战区的干员,则发送至作战区对应位置"; 01 A1 row 7). 风丸 needs 2. Elites never merge. The equipment of the merged copies returns to the hand. Reward: the shop temporarily shows 3 operators of tier min(shopLv+1, 6) at price **0**; take 1; no refresh or freeze; gone at round end. A purchase that completes a merge is allowed with a full hand. [VERIFIED]
|
**Merge:** 3 copies of the same chess (board or hand) merge into 1 elite (`_b`), sent to the **hand** — or, when a consumed copy was deployed, to **that copy's board position** (PRTS 卫戍协议/帮助 "若消耗已部署至作战区的干员,则发送至作战区对应位置"; 01 A1 row 7). 风丸 needs 2. Elites never merge. The equipment of the merged copies returns to the hand. Reward: the shop temporarily shows 3 operators of tier min(shopLv+1, 6) at price **0**; take 1; no refresh or freeze; gone at round end. A full regular hand refuses every purchase, also one that would complete a merge (PRTS 卫戍协议/帮助 §手牌区 "例如招募/购入等通常情况下会增加手牌的操作"; corrected in 0.2.0, GitHub #82 — this line used to allow it). [VERIFIED]
|
||||||
|
|
||||||
## 4. Board, hand, combat
|
## 4. Board, hand, combat
|
||||||
|
|
||||||
|
|||||||
@@ -841,7 +841,7 @@ Local copies are in `scratchpad/gd/extra/critic/` (`bw_盟约.txt`, `bw_sweep.tx
|
|||||||
| 4.1 | round-start auto refresh [ASSUMED] | Confirmed: "进入此阶段时,触发<进入休整期时>的效果,分配一定资金,刷新调度中心未被冻结的位置". Income is granted **before** the 机变 phase: funds are visible behind every 机变 overlay screenshot. | VERIFIED |
|
| 4.1 | round-start auto refresh [ASSUMED] | Confirmed: "进入此阶段时,触发<进入休整期时>的效果,分配一定资金,刷新调度中心未被冻结的位置". Income is granted **before** the 机变 phase: funds are visible behind every 机变 overlay screenshot. | VERIFIED |
|
||||||
| 4.2 / 14 | freeze per slot [ASSUMED] | **One 冻结 button freezes ALL unsold slots**, operators and item: "【冻结】会将当前整备区的全部未售出栏位冻结,将其保留至下一回合。点击【解冻】可以解除该操作". The freeze is consumed at the next round start. In the official intro, frozen cards reappear in R3 and the button reads 冻结 again. A manual refresh is allowed while frozen (tip 4) and rerolls every slot, frozen ones included (2024 rule "重新调配包括冻结中的所有干员和道具"). Keep a per-slot `frozen` flag internally, because 梓兰 freezes one card. | VERIFIED |
|
| 4.2 / 14 | freeze per slot [ASSUMED] | **One 冻结 button freezes ALL unsold slots**, operators and item: "【冻结】会将当前整备区的全部未售出栏位冻结,将其保留至下一回合。点击【解冻】可以解除该操作". The freeze is consumed at the next round start. In the official intro, frozen cards reappear in R3 and the button reads 冻结 again. A manual refresh is allowed while frozen (tip 4) and rerolls every slot, frozen ones included (2024 rule "重新调配包括冻结中的所有干员和道具"). Keep a per-slot `frozen` flag internally, because 梓兰 freezes one card. | VERIFIED |
|
||||||
| new | – | At **combat start, all unfrozen shop contents are cleared** ("进入作战期后,栏上若未处于冻结状态则清空全部物资"). | VERIFIED |
|
| new | – | At **combat start, all unfrozen shop contents are cleared** ("进入作战期后,栏上若未处于冻结状态则清空全部物资"). | VERIFIED |
|
||||||
| 4.2 / 8 | bench 10 + temp area | **Hand (手牌区/整备区) = 10 regular + 5 temporary slots**, shared by operators, **items** and hand-deployable **summons**. Several summons of one owner stack into 1 slot; summons of different owners don't stack.<br>When the regular hand is full, every action that would add a card is refused: buy, and withdraw from the board. Exception: a purchase that completes a 3-merge nets −1 card, so allow it.<br>Passive gains (merge result, band/trait/item grants, items returned by promotion) overflow into the temp slots. **While temp is non-empty the player cannot start combat.** Temp is wiped when the next phase starts.<br>A board↔hand swap is allowed even with a full hand. Cards fill hand slots **right→left**. | VERIFIED |
|
| 4.2 / 8 | bench 10 + temp area | **Hand (手牌区/整备区) = 10 regular + 5 temporary slots**, shared by operators, **items** and hand-deployable **summons**. Several summons of one owner stack into 1 slot; summons of different owners don't stack.<br>When the regular hand is full, every action that would add a card is refused: buy (also one that would complete a 3-merge — PRTS "例如招募/购入等通常情况下会增加手牌的操作"; GitHub #82 corrected the old exception), and withdraw from the board.<br>Passive gains (merge result, band/trait/item grants, items returned by promotion) overflow into the temp slots, and move into a regular slot as soon as one is free ("常规手牌区出现空位时自动移入"). **While temp is non-empty the player cannot start combat.** Temp is wiped when the next phase starts.<br>A board↔hand swap is allowed even with a full hand. Cards fill hand slots **right→left**. | VERIFIED |
|
||||||
| 5.2 | – | **Items cannot be sold** ("多余的装备无法进行出售,可以销毁"). They can only be destroyed for 0 funds. Operators sell for 1 from board or hand. | VERIFIED |
|
| 5.2 | – | **Items cannot be sold** ("多余的装备无法进行出售,可以销毁"). They can only be destroyed for 0 funds. Operators sell for 1 from board or hand. | VERIFIED |
|
||||||
| 7 | promotion reward [COMM] | Confirmed (PRTS 帮助, BWIKI, official intro). On acquiring the 3rd copy:<br>1. The 3rd copy is not created, and the 2 owned copies are destroyed wherever they are.<br>2. **1 elite goes to the hand — or, when a consumed copy was deployed, to that copy's board position** (PRTS 帮助 "发送1名【精锐】状态的该干员至手牌区(若消耗已部署至作战区的干员,则发送至作战区对应位置)"; the user confirmed it first-hand after playtest #6 — this row used to read "not to a board tile", a misreading of the same sentence). Their equipment returns to the hand ("干员晋级后已配发装备会回收至整备区,需要重新配置"). [ASSUMED] where the text is silent: with two or three deployed copies the elite takes the tile of the one that deploys first (the left column first, top to bottom — the official deploy order, §4.3) with its facing; the copies' summons leave and the elite's own summon card joins the hand (§战斗部署 "如果部署的干员拥有可手动部署的附属召唤物,则该召唤物会立刻加入手牌区"); the deploy count never grows. A 突变细胞 carrier is destroyed before its new operator is gained (official footage, DESIGN §21.1), so a merge that operator completes never uses the carrier's tile.<br>3. The shop then temporarily shows **3 operators of tier min(shopLevel+1, 6) at price 0**. The official intro shows "II 0 ×3" after a tier-I merge at L1 and "III 0 ×3" at L2.<br>4. The player takes **1**. The offer cannot be refreshed or frozen and expires at round end. | VERIFIED (elite to the copy's board position: PRTS + the user); the tile among several deployed copies ASSUMED |
|
| 7 | promotion reward [COMM] | Confirmed (PRTS 帮助, BWIKI, official intro). On acquiring the 3rd copy:<br>1. The 3rd copy is not created, and the 2 owned copies are destroyed wherever they are.<br>2. **1 elite goes to the hand — or, when a consumed copy was deployed, to that copy's board position** (PRTS 帮助 "发送1名【精锐】状态的该干员至手牌区(若消耗已部署至作战区的干员,则发送至作战区对应位置)"; the user confirmed it first-hand after playtest #6 — this row used to read "not to a board tile", a misreading of the same sentence). Their equipment returns to the hand ("干员晋级后已配发装备会回收至整备区,需要重新配置"). [ASSUMED] where the text is silent: with two or three deployed copies the elite takes the tile of the one that deploys first (the left column first, top to bottom — the official deploy order, §4.3) with its facing; the copies' summons leave and the elite's own summon card joins the hand (§战斗部署 "如果部署的干员拥有可手动部署的附属召唤物,则该召唤物会立刻加入手牌区"); the deploy count never grows. A 突变细胞 carrier is destroyed before its new operator is gained (official footage, DESIGN §21.1), so a merge that operator completes never uses the carrier's tile.<br>3. The shop then temporarily shows **3 operators of tier min(shopLevel+1, 6) at price 0**. The official intro shows "II 0 ×3" after a tier-I merge at L1 and "III 0 ×3" at L2.<br>4. The player takes **1**. The offer cannot be refreshed or frozen and expires at round end. | VERIFIED (elite to the copy's board position: PRTS + the user); the tile among several deployed copies ASSUMED |
|
||||||
| 2 | BAND_CHECK "3 random + reroll" [ASSUMED] | **All unlocked bands are shown.** In co-op, players pick **one at a time in a random order** shown on the left ("以随机顺序进行策略轮选"). Each co-op player may **skip once**. The step lasts 50 s; on timeout the band is 华法琳. **Duplicates are allowed:** the official illustration shows 2 teammates' picks still un-greyed in the grid. [ASSUMED] 12 s per turn inside the 50 s step. | VERIFIED except the per-turn timer |
|
| 2 | BAND_CHECK "3 random + reroll" [ASSUMED] | **All unlocked bands are shown.** In co-op, players pick **one at a time in a random order** shown on the left ("以随机顺序进行策略轮选"). Each co-op player may **skip once**. The step lasts 50 s; on timeout the band is 华法琳. **Duplicates are allowed:** the official illustration shows 2 teammates' picks still un-greyed in the grid. [ASSUMED] 12 s per turn inside the 50 s step. | VERIFIED except the per-turn timer |
|
||||||
|
|||||||
@@ -438,7 +438,9 @@ function startCombat(phase) {
|
|||||||
for (const p of board.slice(0, 5)) {
|
for (const p of board.slice(0, 5)) {
|
||||||
const c = data.lookup('chess', pick(S.pool).chessId);
|
const c = data.lookup('chess', pick(S.pool).chessId);
|
||||||
const r = boss ? p.row - 7 : p.row;
|
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);
|
const enemyKeys = S.priv.nextEnemies.map((e) => e.enemyKey);
|
||||||
@@ -448,7 +450,7 @@ function startCombat(phase) {
|
|||||||
const key = enemyKeys[i % enemyKeys.length];
|
const key = enemyKeys[i % enemyKeys.length];
|
||||||
const e = data.lookup('enemies', key);
|
const e = data.lookup('enemies', key);
|
||||||
const y = boss ? [2, 5, 3][i % 3] : [9, 12][i % 2];
|
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 });
|
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) {
|
if (boss) {
|
||||||
@@ -456,7 +458,8 @@ function startCombat(phase) {
|
|||||||
const e = data.lookup('enemies', bossRec.enemyKey);
|
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 });
|
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 });
|
store.patch('match', { field });
|
||||||
const allyState = units.map((u) => ({ ...u, hp: u.maxHp * (0.55 + rnd() * 0.45), sp: rnd() * 20, spMax: 20 }));
|
const allyState = units.map((u) => ({ ...u, hp: u.maxHp * (0.55 + rnd() * 0.45), sp: rnd() * 20, spMax: 20 }));
|
||||||
const t0 = performance.now();
|
const t0 = performance.now();
|
||||||
|
|||||||
@@ -362,11 +362,13 @@ function MatchScreen() {
|
|||||||
const shownMembers = shownField ? ((Array.isArray(pub?.fields) ? pub.fields : []).find((f) => f && f.fieldId === shownField.fieldId)?.players
|
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;
|
|| (Array.isArray(shownField.players) ? shownField.players : null)) : null;
|
||||||
const ownView = showPrep || (shownField ? (shownMembers ? shownMembers.includes(myId) : shownField.fieldId === ownFieldId(myId)) : !watchingOther);
|
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(() => {
|
useEffect(() => {
|
||||||
if (!view) return;
|
if (!view) return;
|
||||||
const st = ownView ? ownStage : baseStage;
|
const st = fieldStage || (ownView ? ownStage : baseStage);
|
||||||
if (st) view.setStage(st);
|
if (st) view.setStage(st);
|
||||||
}, [view, ownView, ownStage, baseStage]);
|
}, [view, ownView, ownStage, baseStage, fieldStage]);
|
||||||
const staleFieldRef = useRef(null);
|
const staleFieldRef = useRef(null);
|
||||||
const enteredFieldRef = useRef(null);
|
const enteredFieldRef = useRef(null);
|
||||||
const pressSel = useRef(null); // the selected piece when the current field press began
|
const pressSel = useRef(null); // the selected piece when the current field press began
|
||||||
|
|||||||
@@ -731,15 +731,16 @@ export function mergeTarget(priv, chessId, getChess = () => null) {
|
|||||||
return { row: board[0].row, col: board[0].col, dir: board[0].dir || 'RIGHT' };
|
return { row: board[0].row, col: board[0].col, dir: board[0].dir || 'RIGHT' };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Whether every hand slot is taken (the server refuses a purchase / reward that needs a slot: HAND_FULL). */
|
/** Whether every hand slot is taken (the server refuses every purchase / reward pick then: HAND_FULL, GitHub #82). */
|
||||||
export function handFull(priv) {
|
export function handFull(priv) {
|
||||||
const hand = Array.isArray(priv?.hand) ? priv.hand : [];
|
const hand = Array.isArray(priv?.hand) ? priv.hand : [];
|
||||||
return hand.length >= GEO.HAND_SIZE && hand.every((p) => p != null);
|
return hand.length >= GEO.HAND_SIZE && hand.every((p) => p != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Would gaining one more `slot` (shop / reward card: { kind: 'chess'|'item', id }) complete a merge right away, so it
|
* Would gaining one more `slot` (shop / reward card: { kind: 'chess'|'item', id }) complete a merge right away? Mirror
|
||||||
* needs no hand slot? Mirror of server PlayerState completesChessMerge / completesItemMerge.
|
* of server PlayerState completesChessMerge / completesItemMerge. (A full hand refuses such a purchase all the same:
|
||||||
|
* PRTS 卫戍协议/帮助 §手牌区, GitHub #82.)
|
||||||
* @param {any} priv
|
* @param {any} priv
|
||||||
* @param {{kind?:string, id:string}} slot
|
* @param {{kind?:string, id:string}} slot
|
||||||
* @param {{ getChess?:(id:string)=>any, getItem?:(id:string)=>any, itemMergeCount?: number }} [o]
|
* @param {{ getChess?:(id:string)=>any, getItem?:(id:string)=>any, itemMergeCount?: number }} [o]
|
||||||
@@ -795,10 +796,10 @@ export function offerHeader(offer) {
|
|||||||
/**
|
/**
|
||||||
* Why a shop action is unavailable (null when available).
|
* Why a shop action is unavailable (null when available).
|
||||||
* @param {'buy'|'reward'|'refresh'|'freeze'|'levelUp'|'ready'} kind
|
* @param {'buy'|'reward'|'refresh'|'freeze'|'levelUp'|'ready'} kind
|
||||||
* @param {{ priv:any, editable:boolean, slot?:any, getChess?:(id:string)=>any, getItem?:(id:string)=>any }} ctx
|
* @param {{ priv:any, editable:boolean, slot?:any }} ctx
|
||||||
* @returns {string|null} Chinese reason
|
* @returns {string|null} Chinese reason
|
||||||
*/
|
*/
|
||||||
export function shopBlockReason(kind, { priv, editable, slot, getChess, getItem } = {}) {
|
export function shopBlockReason(kind, { priv, editable, slot } = {}) {
|
||||||
if (!priv) return '尚未就绪';
|
if (!priv) return '尚未就绪';
|
||||||
if (priv.alive === false) return '你已被淘汰';
|
if (priv.alive === false) return '你已被淘汰';
|
||||||
if (kind === 'ready') return priv.canReady === false ? '临时整备区不为空,请先处理溢出的资源' : null;
|
if (kind === 'ready') return priv.canReady === false ? '临时整备区不为空,请先处理溢出的资源' : null;
|
||||||
@@ -811,7 +812,8 @@ export function shopBlockReason(kind, { priv, editable, slot, getChess, getItem
|
|||||||
if (kind === 'buy' || kind === 'reward') {
|
if (kind === 'buy' || kind === 'reward') {
|
||||||
if (!isObj(slot) || slot.sold) return kind === 'reward' ? '已选择' : '已售出';
|
if (!isObj(slot) || slot.sold) return kind === 'reward' ? '已选择' : '已售出';
|
||||||
if ((Number(slot.price) || 0) > funds) return '资金不足';
|
if ((Number(slot.price) || 0) > funds) return '资金不足';
|
||||||
if (handFull(priv) && !completesMerge(priv, slot, { getChess, getItem })) return '整备区已满';
|
// a full hand refuses every purchase / pick, a merge-completing one too (PRTS 卫戍协议/帮助 §手牌区, GitHub #82)
|
||||||
|
if (handFull(priv)) return '整备区已满';
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (kind === 'refresh') return (Number(shop.refreshPrice) || 0) > funds ? '资金不足' : null;
|
if (kind === 'refresh') return (Number(shop.refreshPrice) || 0) > funds ? '资金不足' : null;
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ function PriceHex({ slot, free, poor = false }) {
|
|||||||
title=${poor ? '资金不足' : tone === 'discount' ? `折扣价(原价 ${slot.basePrice})` : tone === 'premium' ? `加价(原价 ${slot.basePrice})` : '价格'} />`;
|
title=${poor ? '资金不足' : tone === 'discount' ? `折扣价(原价 ${slot.basePrice})` : tone === 'premium' ? `加价(原价 ${slot.basePrice})` : '价格'} />`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Data lookups for shopBlockReason (full-hand purchases that complete a merge stay allowed). */
|
/** Data lookups (merge progress / target, the loadout; shopBlockReason ignores them — a full hand refuses every purchase). */
|
||||||
const LOOKUPS = { getChess: (id) => data.lookup('chess', id), getItem: (id) => data.lookup('items', id) };
|
const LOOKUPS = { getChess: (id) => data.lookup('chess', id), getItem: (id) => data.lookup('items', id) };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,9 +2,12 @@
|
|||||||
//
|
//
|
||||||
// Handlers validate → mutate → recompute bonds → mark the private view dirty. They never throw on bad input; they
|
// Handlers validate → mutate → recompute bonds → mark the private view dirty. They never throw on bad input; they
|
||||||
// return `{ ok: true }` or `{ error: ERR.*, detail? }`. Rules (research 00-INDEX §3–§4, 01 A1, 04 §2):
|
// return `{ ok: true }` or `{ error: ERR.*, detail? }`. Rules (research 00-INDEX §3–§4, 01 A1, 04 §2):
|
||||||
// * Hand (整备区) 10 slots filled right→left, temp (临时整备区) 5 slots. A full hand refuses buys / withdrawals,
|
// * Hand (整备区) 10 slots filled right→left, temp (临时整备区) 5 slots. A full hand refuses buys and reward picks —
|
||||||
// except a purchase that completes a merge and a withdrawal whose own summon stack frees a slot. Passive gains (merge
|
// also one that would complete a merge at once (PRTS 卫戍协议/帮助 §手牌区 "例如招募/购入等通常情况下会增加手牌的操作";
|
||||||
// results, grants, returned equipment) overflow into temp; temp blocks Ready ("直到溢出情况排除才可开始进行作战").
|
// GitHub #82) — and withdrawals, except one whose own summon stack frees a slot. Passive gains (merge results,
|
||||||
|
// grants, returned equipment) overflow into temp, and a free hand slot pulls them back in at once, right→left
|
||||||
|
// (_fillHandFromTemp at every recompute: "常规手牌区出现空位时自动移入"); temp blocks Ready ("直到溢出情况排除才可开始
|
||||||
|
// 进行作战").
|
||||||
// A temp piece is resolved (chess sold back to the pool, items destroyed, summon stacks removed — they come back at
|
// A temp piece is resolved (chess sold back to the pool, items destroyed, summon stacks removed — they come back at
|
||||||
// the next round start, grantTokensFor) at the deadline of the
|
// the next round start, grantTokensFor) at the deadline of the
|
||||||
// first prep in which the player could act on it (tempDue): a piece that overflowed during a prep before Ready
|
// first prep in which the player could act on it (tempDue): a piece that overflowed during a prep before Ready
|
||||||
|
|||||||
+26
-15
@@ -1432,6 +1432,11 @@ function* buyLoopSteps(m, ps, { fillOnly = false, maxRefreshes = 0 } = {}) {
|
|||||||
// shed is pre-emptive (one slot is still free), so with only fresh singles it waits for the next prep.
|
// shed is pre-emptive (one slot is still free), so with only fresh singles it waits for the next prep.
|
||||||
const used = ps.hand.filter(Boolean).length;
|
const used = ps.hand.filter(Boolean).length;
|
||||||
if (used >= gd.benchSize - 1) sellWeakestHand(m, ps, { keepFresh: true });
|
if (used >= gd.benchSize - 1) sellWeakestHand(m, ps, { keepFresh: true });
|
||||||
|
// a full hand refuses every purchase, the third copy of a held pair too (PlayerState.buy, PRTS 卫戍协议/帮助 §手牌区):
|
||||||
|
// an affordable one sheds the weakest hand single first (like the shed above, never one of this prep)
|
||||||
|
if (freeSlot(ps.hand) < 0 && ps.shop.slots.some((s) => s && !s.sold && s.kind === 'chess' && ps.priceOf(s) <= ps.funds && ps.completesChessMerge(s.id))) {
|
||||||
|
sellWeakestHand(m, ps, { handOnly: true, keepFresh: true });
|
||||||
|
}
|
||||||
let best = -1;
|
let best = -1;
|
||||||
let bestS = 0;
|
let bestS = 0;
|
||||||
let bestMerges = false;
|
let bestMerges = false;
|
||||||
@@ -1443,7 +1448,7 @@ function* buyLoopSteps(m, ps, { fillOnly = false, maxRefreshes = 0 } = {}) {
|
|||||||
let sc;
|
let sc;
|
||||||
if (s.kind === 'chess') {
|
if (s.kind === 'chess') {
|
||||||
const merges = ps.completesChessMerge(s.id);
|
const merges = ps.completesChessMerge(s.id);
|
||||||
if ((freeSlot(ps.hand) < 0 || mergesOnly) && !merges) return;
|
if (freeSlot(ps.hand) < 0 || (mergesOnly && !merges)) return;
|
||||||
const base = gd.baseIdOf(s.id);
|
const base = gd.baseIdOf(s.id);
|
||||||
if (boardFull && !merges && ps.funds - price < reserve) return; // banked (坎诺特)
|
if (boardFull && !merges && ps.funds - price < reserve) return; // banked (坎诺特)
|
||||||
sc = buyScore(m, ps, s.id, ctx) - price;
|
sc = buyScore(m, ps, s.id, ctx) - price;
|
||||||
@@ -1466,7 +1471,7 @@ function* buyLoopSteps(m, ps, { fillOnly = false, maxRefreshes = 0 } = {}) {
|
|||||||
} else {
|
} else {
|
||||||
if (fillOnly) return;
|
if (fillOnly) return;
|
||||||
if (!canUseItem(m, ps, { id: s.id })) return;
|
if (!canUseItem(m, ps, { id: s.id })) return;
|
||||||
if (freeSlot(ps.hand) < 0 && !ps.completesItemMerge(s.id)) return;
|
if (freeSlot(ps.hand) < 0) return;
|
||||||
if (ps.funds - price < reserve && !ps.completesItemMerge(s.id)) return;
|
if (ps.funds - price < reserve && !ps.completesItemMerge(s.id)) return;
|
||||||
const carriers = [...ps.board.values()].filter((p) => p.kind === 'chess' && (p.items || []).length < gd.equipPerChess).length;
|
const carriers = [...ps.board.values()].filter((p) => p.kind === 'chess' && (p.items || []).length < gd.equipPerChess).length;
|
||||||
if (!carriers) return;
|
if (!carriers) return;
|
||||||
@@ -1805,34 +1810,40 @@ function equipItems(m, ps) {
|
|||||||
/**
|
/**
|
||||||
* Free a hand slot for an item worth keeping (突变细胞): sell the weakest single hand chess, else (on a bot's own seat
|
* Free a hand slot for an item worth keeping (突变细胞): sell the weakest single hand chess, else (on a bot's own seat
|
||||||
* only — a human's items under AI 托管 are never destroyed to make room, like freeHandSlot) destroy the cheapest other
|
* only — a human's items under AI 托管 are never destroyed to make room, like freeHandSlot) destroy the cheapest other
|
||||||
* hand item, else sell the weakest hand chess even of a pair. false: the hand stays full (summon cards only).
|
* hand item, else sell the weakest hand chess even of a pair. `piece` (in temp): done once the freed slot pulled it in
|
||||||
|
* (PlayerState._fillHandFromTemp — resolveTemp asks for the rightmost temp piece, the next one a slot pulls in). false:
|
||||||
|
* the hand stays full (summon cards only).
|
||||||
*/
|
*/
|
||||||
function makeHandRoom(m, ps) {
|
function makeHandRoom(m, ps, piece = null) {
|
||||||
if (freeSlot(ps.hand) >= 0) return true;
|
const room = () => freeSlot(ps.hand) >= 0 || (piece != null && !ps.temp.includes(piece));
|
||||||
if (sellWeakestHand(m, ps, { handOnly: true }) && freeSlot(ps.hand) >= 0) return true;
|
if (room()) return true;
|
||||||
|
if (sellWeakestHand(m, ps, { handOnly: true }) && room()) return true;
|
||||||
const gd = m.gd;
|
const gd = m.gd;
|
||||||
const junk = ps.isBot && !ps.autoplay ? ps.hand.filter((p) => p && p.kind === 'item' && !isMutationCell(gd, p.id))
|
const junk = ps.isBot && !ps.autoplay ? ps.hand.filter((p) => p && p.kind === 'item' && !isMutationCell(gd, p.id))
|
||||||
.sort((a, b) => ((gd.item(a.id) || {}).price || 0) - ((gd.item(b.id) || {}).price || 0) || a.uid - b.uid)[0] : null;
|
.sort((a, b) => ((gd.item(a.id) || {}).price || 0) - ((gd.item(b.id) || {}).price || 0) || a.uid - b.uid)[0] : null;
|
||||||
if (junk && tryDo(() => ps.destroy(junk.uid)) && freeSlot(ps.hand) >= 0) return true;
|
if (junk && tryDo(() => ps.destroy(junk.uid)) && room()) return true;
|
||||||
return sellWeakestHand(m, ps, { handOnly: true, keepPairs: false }) && freeSlot(ps.hand) >= 0;
|
return sellWeakestHand(m, ps, { handOnly: true, keepPairs: false }) && room();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty the temp row before Ready. A freed hand slot pulls the temp pieces in by itself, rightmost first
|
||||||
|
* (PlayerState._fillHandFromTemp), so the pieces are decided right → left: the one at hand is always the next a freed
|
||||||
|
* slot takes. A chess: the weakest bench single is sold to make room (it may be the temp chess itself), else the temp
|
||||||
|
* chess is sold; an item: see below, else destroyed.
|
||||||
|
*/
|
||||||
function resolveTemp(m, ps) {
|
function resolveTemp(m, ps) {
|
||||||
for (let i = 0; i < ps.temp.length; i++) {
|
for (let i = ps.temp.length - 1; i >= 0; i--) {
|
||||||
const p = ps.temp[i];
|
const p = ps.temp[i];
|
||||||
if (!p) continue;
|
if (!p) continue;
|
||||||
const idx = freeSlot(ps.hand);
|
const idx = freeSlot(ps.hand); // (a recompute pulls it in; a state set up without one)
|
||||||
if (idx >= 0 && tryDo(() => ps.move(p.uid, { area: 'hand', idx }))) continue;
|
if (idx >= 0 && tryDo(() => ps.move(p.uid, { area: 'hand', idx }))) continue;
|
||||||
if (p.kind === 'chess') {
|
if (p.kind === 'chess') {
|
||||||
if (!sellWeakestHand(m, ps)) tryDo(() => ps.sell(p.uid));
|
if (!sellWeakestHand(m, ps)) tryDo(() => ps.sell(p.uid));
|
||||||
else {
|
|
||||||
const j = freeSlot(ps.hand);
|
|
||||||
if (j >= 0) tryDo(() => ps.move(p.uid, { area: 'hand', idx: j }));
|
|
||||||
}
|
|
||||||
} else if (p.kind === 'item') {
|
} else if (p.kind === 'item') {
|
||||||
// 突变细胞 comes back after every transformation (昆图斯's strategy item), and a human's item under AI 托管 is theirs:
|
// 突变细胞 comes back after every transformation (昆图斯's strategy item), and a human's item under AI 托管 is theirs:
|
||||||
// make room in the hand (sell a bench operator) rather than lose it — only a hand of nothing but items still drops it
|
// make room in the hand (sell a bench operator) rather than lose it — only a hand of nothing but items still drops it
|
||||||
if ((isMutationCell(m.gd, p.id) || ps.autoplay) && makeHandRoom(m, ps)) {
|
if ((isMutationCell(m.gd, p.id) || ps.autoplay) && makeHandRoom(m, ps, p)) {
|
||||||
|
if (!ps.temp.includes(p)) continue;
|
||||||
const j = freeSlot(ps.hand);
|
const j = freeSlot(ps.hand);
|
||||||
if (j >= 0 && tryDo(() => ps.move(p.uid, { area: 'hand', idx: j }))) continue;
|
if (j >= 0 && tryDo(() => ps.move(p.uid, { area: 'hand', idx: j }))) continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,10 @@
|
|||||||
// collectViolations(m) → string[] (empty when every invariant holds):
|
// collectViolations(m) → string[] (empty when every invariant holds):
|
||||||
// pool 0 ≤ left ≤ cap and left + Σ copies held by pieces == cap per base chess; non-pool chess hold 0 copies
|
// pool 0 ≤ left ≤ cap and left + Σ copies held by pieces == cap per base chess; non-pool chess hold 0 copies
|
||||||
// economy funds / pendingFunds non-negative integers, LP finite, shop level in range, prices ≥ 0
|
// economy funds / pendingFunds non-negative integers, LP finite, shop level in range, prices ≥ 0
|
||||||
// pieces unique uids; hand 10 / temp 5 slots; chess carry ≤ equipPerChess known items; a normal piece holds ≤ 1
|
// pieces unique uids; hand 10 / temp 5 slots; temp holds pieces only while the hand is full (a free hand slot pulls
|
||||||
// copy, an elite ≤ goldenCopies; merges are immediate (never `mergeCount` normal copies of one chess, never
|
// a temp piece in, PlayerState._fillHandFromTemp); chess carry ≤ equipPerChess known items; a normal piece
|
||||||
// two copies of a mergeable normal item); every token's owner chess is deployed
|
// holds ≤ 1 copy, an elite ≤ goldenCopies; merges are immediate (never `mergeCount` normal copies of one
|
||||||
|
// chess, never two copies of a mergeable normal item); every token's owner chess is deployed
|
||||||
// board tiles inside the own region and legal for the piece (a range-bound summon inside its owner's attack
|
// board tiles inside the own region and legal for the piece (a range-bound summon inside its owner's attack
|
||||||
// range); no items on the board; chess count ≤ deploy cap
|
// range); no items on the board; chess count ≤ deploy cap
|
||||||
// (where a merge's elite goes — a consumed deployed copy's tile, else the hand — needs the state before the
|
// (where a merge's elite goes — a consumed deployed copy's tile, else the hand — needs the state before the
|
||||||
@@ -52,6 +53,8 @@ export function collectViolations(m, { limit = 25 } = {}) {
|
|||||||
if (ps.alive && m.phase !== PHASE.SETTLE && ps.lp <= 0 && m.teamLp == null && m.round > 0 && ps.bandId) fail(`${id}: alive with lp ${ps.lp}`);
|
if (ps.alive && m.phase !== PHASE.SETTLE && ps.lp <= 0 && m.teamLp == null && m.round > 0 && ps.bandId) fail(`${id}: alive with lp ${ps.lp}`);
|
||||||
if (ps.hand.length !== gd.benchSize) fail(`${id}: hand has ${ps.hand.length} slots`);
|
if (ps.hand.length !== gd.benchSize) fail(`${id}: hand has ${ps.hand.length} slots`);
|
||||||
if (ps.temp.length !== gd.tempSize) fail(`${id}: temp has ${ps.temp.length} slots`);
|
if (ps.temp.length !== gd.tempSize) fail(`${id}: temp has ${ps.temp.length} slots`);
|
||||||
|
// 临时整备区 = overflow only (PRTS 卫戍协议/帮助 §手牌区 "常规手牌区出现空位时自动移入", GitHub #82)
|
||||||
|
if (ps.temp.some(Boolean) && ps.hand.some((x) => x == null)) fail(`${id}: a temp piece waits while a hand slot is free`);
|
||||||
if (!(ps.shop.level >= 1 && ps.shop.level <= gd.maxShopLevel)) fail(`${id}: shop level ${ps.shop.level}`);
|
if (!(ps.shop.level >= 1 && ps.shop.level <= gd.maxShopLevel)) fail(`${id}: shop level ${ps.shop.level}`);
|
||||||
if (!(ps.shop.upgradePrice >= 0)) fail(`${id}: upgradePrice ${ps.shop.upgradePrice}`);
|
if (!(ps.shop.upgradePrice >= 0)) fail(`${id}: upgradePrice ${ps.shop.upgradePrice}`);
|
||||||
if (!Number.isInteger(ps.shop.freeRefreshes) || ps.shop.freeRefreshes < 0) fail(`${id}: freeRefreshes ${ps.shop.freeRefreshes}`);
|
if (!Number.isInteger(ps.shop.freeRefreshes) || ps.shop.freeRefreshes < 0) fail(`${id}: freeRefreshes ${ps.shop.freeRefreshes}`);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { PHASE, GEO } from '../../../shared/constants.js';
|
import { PHASE, GEO } from '../../../shared/constants.js';
|
||||||
import { deriveSeed } from '../../sim/rng.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 { FieldRunner, timelineAt, uniteBillBounds } from '../fields.js';
|
||||||
import { uniteLeft } from '../../sim/spec.js';
|
import { uniteLeft } from '../../sim/spec.js';
|
||||||
import { FLOW_TICKER_PRIORITY, DELAYS } from './common.js';
|
import { FLOW_TICKER_PRIORITY, DELAYS } from './common.js';
|
||||||
@@ -38,7 +38,11 @@ export class MatchUnite {
|
|||||||
this.runner.start();
|
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) {
|
_uniteOpts(plan, limit) {
|
||||||
const { wave, players } = uniteBattleOpts(this, plan, limit);
|
const { wave, players } = uniteBattleOpts(this, plan, limit);
|
||||||
return {
|
return {
|
||||||
@@ -46,7 +50,7 @@ export class MatchUnite {
|
|||||||
kind: 'unite',
|
kind: 'unite',
|
||||||
modeId: this.modeId,
|
modeId: this.modeId,
|
||||||
round: this.round,
|
round: this.round,
|
||||||
stageId: this.stageId,
|
stageId: uniteStageId(this.gd, plan.helpers.length) ?? this.stageId,
|
||||||
rect: { ...GEO.UNITE_RECT },
|
rect: { ...GEO.UNITE_RECT },
|
||||||
timeLimit: limit,
|
timeLimit: limit,
|
||||||
players,
|
players,
|
||||||
|
|||||||
@@ -97,6 +97,12 @@ export class PlayerEconomy {
|
|||||||
this.dirty();
|
this.dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* g.buy {slot}. A full regular hand refuses every purchase, also one whose copy would complete a merge at once (PRTS
|
||||||
|
* 卫戍协议/帮助 §手牌区 "当常规手牌区全满无空位时,玩家将无法执行使手牌溢出的操作" — "例如招募/购入等通常情况下会增加手牌的
|
||||||
|
* 操作"; BWIKI 盟约 "当整备区达到上限后将无法进行涉及到整备区的操作(购买、撤回等)"; GitHub #82: the reporter's first-hand
|
||||||
|
* check in the official game, "哪怕是已经有了两个相同干员情况"). Until 0.1.3 such a purchase was let through.
|
||||||
|
*/
|
||||||
buy(slotIdx) {
|
buy(slotIdx) {
|
||||||
const g = this._gate(); if (g) return g;
|
const g = this._gate(); if (g) return g;
|
||||||
if (!Number.isInteger(slotIdx) || slotIdx < 0 || slotIdx >= this.shop.slots.length) return fail(ERR.BAD_TARGET);
|
if (!Number.isInteger(slotIdx) || slotIdx < 0 || slotIdx >= this.shop.slots.length) return fail(ERR.BAD_TARGET);
|
||||||
@@ -113,13 +119,13 @@ export class PlayerEconomy {
|
|||||||
const base = this.gd.baseIdOf(slot.id);
|
const base = this.gd.baseIdOf(slot.id);
|
||||||
const need = rec.isGolden ? this.gd.goldenCopies : 1;
|
const need = rec.isGolden ? this.gd.goldenCopies : 1;
|
||||||
if (this.m.pool.has(base) && this.m.pool.left(base) < need) return fail(ERR.SOLD_OUT);
|
if (this.m.pool.has(base) && this.m.pool.left(base) < need) return fail(ERR.SOLD_OUT);
|
||||||
if (handFull && !this.completesChessMerge(slot.id)) return fail(ERR.HAND_FULL);
|
if (handFull) return fail(ERR.HAND_FULL);
|
||||||
this.spend(price);
|
this.spend(price);
|
||||||
slot.sold = true;
|
slot.sold = true;
|
||||||
piece = this.acquireChess(slot.id, { source: 'buy' });
|
piece = this.acquireChess(slot.id, { source: 'buy' });
|
||||||
} else {
|
} else {
|
||||||
if (!this.gd.item(slot.id)) return fail(ERR.BAD_TARGET);
|
if (!this.gd.item(slot.id)) return fail(ERR.BAD_TARGET);
|
||||||
if (handFull && !this.completesItemMerge(slot.id)) return fail(ERR.HAND_FULL);
|
if (handFull) return fail(ERR.HAND_FULL);
|
||||||
this.spend(price);
|
this.spend(price);
|
||||||
slot.sold = true;
|
slot.sold = true;
|
||||||
piece = this.acquireItem(slot.id, { source: 'buy' });
|
piece = this.acquireItem(slot.id, { source: 'buy' });
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// server/match/player/pieces.js — PlayerState methods: piece bookkeeping — new pieces, lookup by uid (board / hand /
|
// server/match/player/pieces.js — PlayerState methods: piece bookkeeping — new pieces, lookup by uid (board / hand /
|
||||||
// temp / equipped), the chess locations, detach and stow (hand first, overflow temp), the per-piece round counters
|
// temp / equipped), the chess locations, detach and stow (hand first, overflow temp; a free hand slot pulls a temp piece
|
||||||
// (拉普兰德), pool copies back to the shared pool, and summon stacks (removal, lifting, counts, the stack granted for an
|
// in, _fillHandFromTemp), the per-piece round counters (拉普兰德), pool copies back to the shared pool, and summon stacks
|
||||||
// owner).
|
// (removal, lifting, counts, the stack granted for an owner).
|
||||||
// Installed on PlayerState.prototype by server/match/PlayerState.js (a method container: never instantiated; `this` is
|
// Installed on PlayerState.prototype by server/match/PlayerState.js (a method container: never instantiated; `this` is
|
||||||
// the player state).
|
// the player state).
|
||||||
|
|
||||||
@@ -68,7 +68,8 @@ export class PlayerPieces {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Put a piece into the hand (right→left) or, when `allowTemp`, the temp slots (due at the deadline of the first prep
|
* Put a piece into the hand (right→left) or, when `allowTemp`, the temp slots (due at the deadline of the first prep
|
||||||
* in which the player can act on it, _tempDueNow). Returns 'hand' | 'temp' | null.
|
* in which the player can act on it, _tempDueNow). Returns 'hand' | 'temp' | null. A piece put into temp while a hand
|
||||||
|
* slot is free (`toTemp`) moves into the hand at the next recompute (_fillHandFromTemp).
|
||||||
*/
|
*/
|
||||||
stow(piece, { allowTemp = true, toTemp = false, preferIdx = null } = {}) {
|
stow(piece, { allowTemp = true, toTemp = false, preferIdx = null } = {}) {
|
||||||
if (!toTemp) {
|
if (!toTemp) {
|
||||||
@@ -85,6 +86,31 @@ export class PlayerPieces {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PRTS 卫戍协议/帮助 §手牌区 "溢出单位会自动进入临时手牌区,常规手牌区出现空位时自动移入" (GitHub #82; until 0.1.3 the
|
||||||
|
* player had to drag them back): every free regular hand slot takes a temp piece at once. Order [ASSUMED] (PRTS names
|
||||||
|
* none): the temp row empties in the order it fills — right→left, the way stow fills it (freeSlot), so the piece that
|
||||||
|
* overflowed first moves first — and each piece takes the hand's next free slot right→left ("被发送至手牌区的物资优先
|
||||||
|
* 从右到左填充空位"). recompute() runs it, so it follows every change that frees a slot: a sale, a deployment from the
|
||||||
|
* hand, a merge that consumed hand copies, an item equipped / destroyed / used from the hand, a summon stack removed
|
||||||
|
* with its owner, an effect's destroyPiece… A piece that leaves temp is no longer due (tempDue). Returns the number
|
||||||
|
* moved.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
_fillHandFromTemp() {
|
||||||
|
let moved = 0;
|
||||||
|
for (let j = this.temp.length - 1; j >= 0; j--) {
|
||||||
|
const p = this.temp[j];
|
||||||
|
if (!p) continue;
|
||||||
|
const i = freeSlot(this.hand);
|
||||||
|
if (i < 0) break;
|
||||||
|
this._detach({ piece: p, area: 'temp', idx: j });
|
||||||
|
this.hand[i] = p;
|
||||||
|
moved++;
|
||||||
|
}
|
||||||
|
return moved;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Per-piece counter of the current round (`piece.meta.round` = { r, n: { key: count } }): 0 for a key not counted yet
|
* Per-piece counter of the current round (`piece.meta.round` = { r, n: { key: count } }): 0 for a key not counted yet
|
||||||
* this round. The counters belong to the operator: a move keeps them, a new piece (bought, granted, transformed)
|
* this round. The counters belong to the operator: a move keeps them, a new piece (bought, granted, transformed)
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ export class PlayerPrep {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* g.reward {idx}: the pick of the first queued offer (promotion reward, a strategy's special refresh, an item offer).
|
||||||
|
* A pick is a 招募 like a purchase: a full regular hand refuses it, also when it would complete a merge (PRTS
|
||||||
|
* 卫戍协议/帮助 §手牌区, see buy; GitHub #82).
|
||||||
|
*/
|
||||||
pickReward(idx) {
|
pickReward(idx) {
|
||||||
const g = this._gate(); if (g) return g;
|
const g = this._gate(); if (g) return g;
|
||||||
const offer = this.offers[0];
|
const offer = this.offers[0];
|
||||||
@@ -26,14 +31,14 @@ export class PlayerPrep {
|
|||||||
const handFull = freeSlot(this.hand) < 0;
|
const handFull = freeSlot(this.hand) < 0;
|
||||||
if (slot.kind === 'item') {
|
if (slot.kind === 'item') {
|
||||||
if (!this.gd.item(slot.id)) return fail(ERR.BAD_TARGET);
|
if (!this.gd.item(slot.id)) return fail(ERR.BAD_TARGET);
|
||||||
if (handFull && !this.completesItemMerge(slot.id)) return fail(ERR.HAND_FULL);
|
if (handFull) return fail(ERR.HAND_FULL);
|
||||||
} else {
|
} else {
|
||||||
const rec = this.gd.chess(slot.id);
|
const rec = this.gd.chess(slot.id);
|
||||||
if (!rec) return fail(ERR.BAD_TARGET);
|
if (!rec) return fail(ERR.BAD_TARGET);
|
||||||
const base = this.gd.baseIdOf(slot.id);
|
const base = this.gd.baseIdOf(slot.id);
|
||||||
const need = rec.isGolden ? this.gd.goldenCopies : 1;
|
const need = rec.isGolden ? this.gd.goldenCopies : 1;
|
||||||
if (this.m.pool.has(base) && this.m.pool.left(base) < need) return fail(ERR.SOLD_OUT);
|
if (this.m.pool.has(base) && this.m.pool.left(base) < need) return fail(ERR.SOLD_OUT);
|
||||||
if (handFull && !this.completesChessMerge(slot.id)) return fail(ERR.HAND_FULL);
|
if (handFull) return fail(ERR.HAND_FULL);
|
||||||
}
|
}
|
||||||
const price = Number.isFinite(slot.price) && slot.price > 0 ? Math.trunc(slot.price) : 0;
|
const price = Number.isFinite(slot.price) && slot.price > 0 ? Math.trunc(slot.price) : 0;
|
||||||
if (price > this.funds) return fail(ERR.NO_FUNDS);
|
if (price > this.funds) return fail(ERR.NO_FUNDS);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// server/match/player/round.js — PlayerState methods: the round lifecycle the match calls — startRound (income, pending
|
// server/match/player/round.js — PlayerState methods: the round lifecycle the match calls — startRound (income, pending
|
||||||
// coins, the shop, summon stacks topped up), endPrep, eliminate (every copy back to the pool), recompute (legality,
|
// coins, the shop, summon stacks topped up), endPrep, eliminate (every copy back to the pool), recompute (legality,
|
||||||
// out-of-range summons, bonds), the bond views — and battleInput (the player's PlayerBattleInput: board units with
|
// out-of-range summons, temp pieces into free hand slots, bonds), the bond views — and battleInput (the player's
|
||||||
// their loadout, carried 联防 state, the reached layers).
|
// PlayerBattleInput: board units with their loadout, carried 联防 state, the reached layers).
|
||||||
// Installed on PlayerState.prototype by server/match/PlayerState.js (a method container: never instantiated; `this` is
|
// Installed on PlayerState.prototype by server/match/PlayerState.js (a method container: never instantiated; `this` is
|
||||||
// the player state).
|
// the player state).
|
||||||
|
|
||||||
@@ -74,6 +74,8 @@ export class PlayerRound {
|
|||||||
this.deployMap(); // a change of the deploy field (a boss round's prep) marks the legality stale
|
this.deployMap(); // a change of the deploy field (a boss round's prep) marks the legality stale
|
||||||
if (this._legalityStale) this._evictIllegal();
|
if (this._legalityStale) this._evictIllegal();
|
||||||
this._liftOutOfRange();
|
this._liftOutOfRange();
|
||||||
|
// a free regular hand slot pulls a temp piece in (PRTS 卫戍协议/帮助 §手牌区 "常规手牌区出现空位时自动移入")
|
||||||
|
this._fillHandFromTemp();
|
||||||
this.bonds = computeBonds(this.gd, this);
|
this.bonds = computeBonds(this.gd, this);
|
||||||
this.dirty();
|
this.dirty();
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-1
@@ -5,7 +5,11 @@
|
|||||||
// players chosen by most units on the field (downed included) > has an active bond (存疑) > most undowned units, then
|
// 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
|
// 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
|
// 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
|
// 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 }`, "阵地以其当前状态";
|
// 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
|
// 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);
|
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). */
|
/** Battle options for the unite field (without data/logger, added by the match). */
|
||||||
export function uniteBattleOpts(m, plan, timeLimit) {
|
export function uniteBattleOpts(m, plan, timeLimit) {
|
||||||
const wave = buildUniteWave(m.gd, plan.leaked, plan.helpers.length, timeLimit);
|
const wave = buildUniteWave(m.gd, plan.leaked, plan.helpers.length, timeLimit);
|
||||||
|
|||||||
@@ -654,6 +654,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
|
// 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.
|
// briefing BATTLEFIELD row. Names come from research only, so the build must strip such notes.
|
||||||
for (const s of Object.values(stages)) {
|
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.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.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`);
|
assert.equal(s.name, s.name.trim().replace(/\s+/g, ' '), `${s.id}: stray whitespace in name`);
|
||||||
@@ -662,6 +663,34 @@ test('stages: player-facing names are clean Chinese "战场#NN…" labels (no re
|
|||||||
assert.equal(stages.act2autochess_m01.name, '战场#05(下半) 源石流发生装置');
|
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)', () => {
|
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]; };
|
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)
|
assert.deepEqual(st('chess_char_6_11_b'), [1893, 492, 141, 0, 1, 15]); // 缪尔赛思 精锐 (E2 60 + module Lv3)
|
||||||
|
|||||||
@@ -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 |
|
| `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) |
|
| `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 |
|
| `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
|
Battles go through the production BattleSpec path (`server/sim/spec.js`, as browsers and the server's headless fields
|
||||||
|
|||||||
+39
-44
@@ -1283,7 +1283,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",
|
"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,
|
"fast": true,
|
||||||
"kind": "unite",
|
"kind": "unite",
|
||||||
"stageId": "act1autochess_m02",
|
"stageId": "act1autochess_escaped_single",
|
||||||
"modeId": "mode_multi_normal",
|
"modeId": "mode_multi_normal",
|
||||||
"round": 7,
|
"round": 7,
|
||||||
"seed": 2621626757,
|
"seed": 2621626757,
|
||||||
@@ -1295,34 +1295,33 @@
|
|||||||
"total": 10,
|
"total": 10,
|
||||||
"unspawned": 0,
|
"unspawned": 0,
|
||||||
"bossHpLeft": null,
|
"bossHpLeft": null,
|
||||||
"rngDraws": 24,
|
"rngDraws": 7,
|
||||||
"errors": {"count":0,"kinds":[]},
|
"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": [
|
"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",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",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,3,17564,0,0,4,1],
|
["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,1,7345,0,0,1,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",62.12,1,5,1,632,6072,7510,0,4],
|
["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,8,4,29637,0,0,0,1],
|
["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,1,15325,0,0,8,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",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","~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,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]
|
||||||
["p1","~3","token","token_10017_skadi2_dedant",null,null,100,1,25,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]},
|
"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,24,5100,13,13,48,136,293,10,14,8,8,7,11668,21,0,11,0,1,10,0,1,0],
|
"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":15,"bounty":1,"deploy":13,"die":14,"dmg":60,"fx":58,"heal":124,"skill":16,"spawn":23,"status":65},
|
"events": {"atk":31,"bounty":1,"deploy":14,"die":15,"dmg":79,"fx":40,"heal":73,"skill":11,"spawn":22,"status":39},
|
||||||
"fx": {"bombard":7,"bombardShell":7,"burst":1,"lightning":15,"lock":7,"lure":1,"scorchBurst":4,"snow":10,"storm":1,"talent":1,"tide":4},
|
"fx": {"bombard":1,"bombardShell":1,"burst":1,"camouflage":1,"lightning":14,"lock":1,"lure":1,"snow":14,"storm":1,"talent":1,"tide":3,"wanted":1},
|
||||||
"snaps": "d0a32c2d 14dc64f8 d9ffb8be 0e32f941 f5f88a89 f3e84df2 97783db6 49fb05e7 d960234b ef9c8339 304bd9e6 d7e7110a 8a0636b9 840cbf99 e365c522 1d462726 178ff4dc"
|
"snaps": "55586847 bbe93dd8 5bb5e558 d2646601 7210f915 de297199 991f7dc7 b11d3b0a 9572d24f ca2b027d ff63e562 b8302479 9eb55590 bb0bba8a 5b8d04c8 cab0ad61 6aa45251"
|
||||||
},
|
},
|
||||||
"unite-2": {
|
"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",
|
"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,
|
"fast": true,
|
||||||
"kind": "unite",
|
"kind": "unite",
|
||||||
"stageId": "act1autochess_m07",
|
"stageId": "act1autochess_escaped_multi",
|
||||||
"modeId": "mode_multi_hard",
|
"modeId": "mode_multi_hard",
|
||||||
"round": 11,
|
"round": 11,
|
||||||
"seed": 1995382690,
|
"seed": 1995382690,
|
||||||
@@ -1341,34 +1340,30 @@
|
|||||||
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":11672,"bossDmg":0,"heal":10253,"deaths":0,"layerGains":{}}
|
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":11672,"bossDmg":0,"heal":10253,"deaths":0,"layerGains":{}}
|
||||||
},
|
},
|
||||||
"units": [
|
"units": [
|
||||||
["p1",1,"op","chess_char_6_15_b",2,"uniequip_002_qiubai",4608,1,36,3,32640,4897,0,21,1],
|
["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,39,0,19926,4671,0,15,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,37,0,0,0,0,0,0],
|
["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,13,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,16,5,74645,0,0,15,1],
|
["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,18,0,0,5746,0,0,0],
|
["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,20,0,0,0,5334,72,1],
|
["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,11,0,9334,0,0,9,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,14,0,0,0,0,0,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,6,0,0,0,0,0,0],
|
["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,10,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,38,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,19,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,8,1,11672,0,0,0,1],
|
["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,12,0,0,0,10253,25,2],
|
["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,17,0,0,0,0,0,0],
|
["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,21,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],
|
||||||
["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null],
|
["p1","~1","token","token_10019_nearl2_sword",null,null,5720.79,0,31,1,27958,279,0,4,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]
|
|
||||||
],
|
],
|
||||||
"enemies": {"enemy_10029_vtshld":[1,0,0,0,0],"enemy_1249_lysdb_2":[3,3,0,859,53748],"enemy_1251_lysyta":[4,4,0,0,32846],"enemy_1329_cbshld":[3,3,0,8987,89580]},
|
"enemies": {"enemy_10029_vtshld":[1,0,0,0,0],"enemy_1249_lysdb_2":[3,3,0,859,53748],"enemy_1251_lysyta":[4,4,0,0,32846],"enemy_1329_cbshld":[3,3,0,8987,89580]},
|
||||||
"hooks": [1,38,6900,174,174,313,539,506,11,16,9,7,0,19291,439,5,11,0,1,11,0,0,0],
|
"hooks": [1,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":33,"status":89},
|
"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},
|
"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 4f56b578 010f8693 ca7897bc 279c768c 1d8c9741 d8ab925c 474e2aed 25e7c521 b15154e1 e04d02a8 7f359c91 a32c4824 d77cdf62 dc34029c 99bf46b2 277a7e2a 13be40dc 2eb06e0f f62c09c2 9b9bfcce b6a4c76c d5e7ff8a"
|
"snaps": "87a560e8 45db343c 0d5cd8db e792866d d5cc68e6 078f9bf4 c8fec815 474d7baf 079475d2 d3772234 c93b6998 574ad909 4ebd4b48 1081da2e 5c69184d e8708f8b 098ca455 4c75c9b4 eec01031 7a5056b5 087553f4 7686d406 9672a226"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+116
-123
@@ -511,7 +511,7 @@
|
|||||||
"combat": "client",
|
"combat": "client",
|
||||||
"boost": null,
|
"boost": null,
|
||||||
"setup": {"stageId":"act2autochess_m04","bossId":"boss_3","hiddenBossId":"boss_8","factions":["FLY","TIMES","DOT"]},
|
"setup": {"stageId":"act2autochess_m04","bossId":"boss_3","hiddenBossId":"boss_8","factions":["FLY","TIMES","DOT"]},
|
||||||
"end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":55,"bossPoolLeft":0,"virtualMs":119900},
|
"end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":55,"bossPoolLeft":0,"virtualMs":121400},
|
||||||
"rng": {"setup":25,"shop":449,"waves":0,"draft":40,"bots":21634,"meta":8},
|
"rng": {"setup":25,"shop":449,"waves":0,"draft":40,"bots":21634,"meta":8},
|
||||||
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
|
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
|
||||||
"rounds": [
|
"rounds": [
|
||||||
@@ -568,17 +568,17 @@
|
|||||||
{
|
{
|
||||||
"round": 11,
|
"round": 11,
|
||||||
"ai_0": [1,45,12,0,2,5,8,"360613f3",118,"arcaneShip 2/34, miraShip 1/0, preciShip 0/10, sargonShip 0/10, siracusaShip 1/56, skillfulShip 0/10, soloShip 1/6, suntShip 1/0, swiftShip 1/22",23,23,0,99],
|
"ai_0": [1,45,12,0,2,5,8,"360613f3",118,"arcaneShip 2/34, miraShip 1/0, preciShip 0/10, sargonShip 0/10, siracusaShip 1/56, skillfulShip 0/10, soloShip 1/6, suntShip 1/0, swiftShip 1/22",23,23,0,99],
|
||||||
"ai_1": [1,28,12,0,0,5,8,"07be045d",135,"arcaneShip 2/28, kazimierzShip 1/24, kjeragShip 0/8, preciShip 1/40, sargonShip 0/10, steadShip 1/0, suntShip 1/0, swiftShip 1/43",23,23,0,104]
|
"ai_1": [1,28,12,0,0,5,8,"bfa85f5f",135,"arcaneShip 2/28, kazimierzShip 1/24, kjeragShip 0/8, preciShip 1/40, sargonShip 0/10, steadShip 1/0, suntShip 1/0, swiftShip 1/43",20,23,3,104]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"round": 12,
|
"round": 12,
|
||||||
"ai_0": [1,45,14,0,0,6,8,"d434f9ef",168,"arcaneShip 2/46, miraShip 1/6, preciShip 0/10, sargonShip 0/10, siracusaShip 1/68, skillfulShip 1/18, soloShip 1/6, suntShip 1/0, swiftShip 1/24",105,105,0,113],
|
"ai_0": [1,45,14,0,0,6,8,"d434f9ef",168,"arcaneShip 2/46, miraShip 1/6, preciShip 0/10, sargonShip 0/10, siracusaShip 1/68, skillfulShip 1/18, soloShip 1/6, suntShip 1/0, swiftShip 1/24",105,105,0,113],
|
||||||
"ai_1": [1,28,12,0,0,6,8,"e37fca4e",161,"arcaneShip 2/28, kazimierzShip 1/32, kjeragShip 0/8, preciShip 1/45, sargonShip 0/10, soloShip 1/0, suntShip 1/0, swiftShip 1/56",105,105,0,115]
|
"ai_1": [1,28,12,0,0,6,8,"a81bef38",161,"arcaneShip 2/28, kazimierzShip 1/32, kjeragShip 0/8, preciShip 1/45, sargonShip 0/10, soloShip 1/0, suntShip 1/0, swiftShip 1/56",105,105,0,115]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"round": 13,
|
"round": 13,
|
||||||
"ai_0": [1,37,12,0,2,6,8,"5deeb03a",214,"arcaneShip 2/70, miraShip 1/12, preciShip 0/10, sargonShip 0/10, siracusaShip 1/98, skillfulShip 1/28, soloShip 1/6, suntShip 1/0, swiftShip 0/32",26,34,8,125],
|
"ai_0": [1,37,12,0,2,6,8,"5deeb03a",214,"arcaneShip 2/70, miraShip 1/12, preciShip 0/10, sargonShip 0/10, siracusaShip 1/98, skillfulShip 1/28, soloShip 1/6, suntShip 1/0, swiftShip 0/32",26,34,8,125],
|
||||||
"ai_1": [1,18,12,0,0,6,8,"41dae901",195,"arcaneShip 2/36, kazimierzShip 1/40, kjeragShip 0/8, preciShip 1/50, sargonShip 0/10, steadShip 1/0, suntShip 1/0, swiftShip 1/69",24,34,10,127]
|
"ai_1": [1,18,12,0,0,6,8,"49c3b995",195,"arcaneShip 2/36, kazimierzShip 1/40, kjeragShip 0/8, preciShip 1/50, sargonShip 0/10, steadShip 1/0, suntShip 1/0, swiftShip 1/69",22,34,12,127]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"players": {
|
"players": {
|
||||||
@@ -592,7 +592,7 @@
|
|||||||
"title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"},
|
"title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"},
|
||||||
"trophies": 4,
|
"trophies": 4,
|
||||||
"reward": 400,
|
"reward": 400,
|
||||||
"stats": {"activatedLayers":250,"bossDamage":287418,"dmgDealt":2928162,"gold":139,"itemsEquipped":12,"kills":331,"leaks":9,"lpLost":8,"merges":5,"perfectRounds":11,"refreshes":23},
|
"stats": {"activatedLayers":250,"bossDamage":287418,"dmgDealt":3020132,"gold":139,"itemsEquipped":12,"kills":334,"leaks":9,"lpLost":8,"merges":5,"perfectRounds":11,"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"],
|
"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"]
|
"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"]
|
||||||
},
|
},
|
||||||
@@ -606,8 +606,8 @@
|
|||||||
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
|
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
|
||||||
"trophies": 4,
|
"trophies": 4,
|
||||||
"reward": 400,
|
"reward": 400,
|
||||||
"stats": {"activatedLayers":205,"bossDamage":421332,"dmgDealt":3168659,"gold":139,"itemsEquipped":16,"kills":317,"leaks":13,"lpLost":10,"merges":5,"perfectRounds":10,"refreshes":13},
|
"stats": {"activatedLayers":205,"bossDamage":421332,"dmgDealt":3034736,"gold":139,"itemsEquipped":16,"kills":312,"leaks":18,"lpLost":10,"merges":5,"perfectRounds":9,"refreshes":13},
|
||||||
"lineup": ["4_20_a@12,8[4_03_e_b+3_06_e_a]","1_17_b@11,7","4_02_b@11,8[4_08_e_a]","4_01_b@10,7[1_02_e_a]","6_17_a@10,8[4_06_e_a+1_05_e_a]","6_19_a@9,7[3_08_e_a+1_01_e_b]","4_14_a@9,8","5_07_a@9,9"],
|
"lineup": ["4_20_a@12,8[4_03_e_b+3_06_e_a]","1_17_b@11,7","4_02_b@11,8[4_08_e_a]","4_01_b@10,7[1_02_e_a]","6_17_a@10,8[1_01_e_b+4_06_e_a]","6_19_a@9,7[1_05_e_a+3_08_e_a]","4_14_a@9,8","5_07_a@9,9"],
|
||||||
"bonds": ["arcaneShip 2/36","kazimierzShip 1/40","kjeragShip 0/8","preciShip 2/55","raidShip 1/0","sargonShip 0/10","suntShip 1/0","swiftShip 1/74"]
|
"bonds": ["arcaneShip 2/36","kazimierzShip 1/40","kjeragShip 0/8","preciShip 2/55","raidShip 1/0","sargonShip 0/10","suntShip 1/0","swiftShip 1/74"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -664,22 +664,22 @@
|
|||||||
{
|
{
|
||||||
"round": 8,
|
"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_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,
|
"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_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,19,12,0,1,4,8,"f01fb873",39,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 1/28, sargonShip 0/4, siracusaShip 1/4, skillfulShip 1/0, soloShip 1/0, swiftShip 1/7",17,23,6,78]
|
"ai_1": [1,18,12,0,1,4,8,"f01fb873",39,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 1/28, sargonShip 0/4, siracusaShip 1/4, skillfulShip 1/0, soloShip 1/0, swiftShip 1/7",17,23,6,78]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"round": 10,
|
"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_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,15,13,0,0,5,8,"ad2dc598",61,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 1/41, 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,14,13,0,0,5,8,"ad2dc598",61,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 1/41, sargonShip 0/4, siracusaShip 1/4, skillfulShip 1/2, soloShip 1/0, suntShip 1/0, swiftShip 1/14",4,8,4,91]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"round": 11,
|
"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",9,27,18,100],
|
"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",9,27,18,100],
|
||||||
"ai_1": [1,5,12,0,0,5,8,"66c45cf0",83,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 1/50, sargonShip 0/4, siracusaShip 1/8, skillfulShip 1/4, soloShip 1/0, suntShip 1/0, swiftShip 1/21",6,27,21,102]
|
"ai_1": [1,4,12,0,0,5,8,"66c45cf0",83,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 1/50, sargonShip 0/4, siracusaShip 1/8, skillfulShip 1/4, soloShip 1/0, suntShip 1/0, swiftShip 1/21",6,27,21,102]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"round": 12,
|
"round": 12,
|
||||||
@@ -703,7 +703,7 @@
|
|||||||
"title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"},
|
"title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"},
|
||||||
"trophies": 3,
|
"trophies": 3,
|
||||||
"reward": 298,
|
"reward": 298,
|
||||||
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2550230,"gold":126,"itemsEquipped":8,"kills":204,"leaks":88,"lpLost":32,"merges":4,"perfectRounds":9,"refreshes":13},
|
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2497540,"gold":126,"itemsEquipped":8,"kills":203,"leaks":88,"lpLost":32,"merges":4,"perfectRounds":9,"refreshes":13},
|
||||||
"lineup": [],
|
"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"]
|
"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":"装备配发数量最多"},
|
"title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"},
|
||||||
"trophies": 2,
|
"trophies": 2,
|
||||||
"reward": 255,
|
"reward": 255,
|
||||||
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1355731,"gold":114,"itemsEquipped":11,"kills":160,"leaks":73,"lpLost":31,"merges":3,"perfectRounds":5,"refreshes":19},
|
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1355731,"gold":114,"itemsEquipped":11,"kills":160,"leaks":73,"lpLost":32,"merges":3,"perfectRounds":5,"refreshes":19},
|
||||||
"lineup": [],
|
"lineup": [],
|
||||||
"bonds": ["arcaneShip 0/2","kazimierzShip 0/2","preciShip 0/59","sargonShip 0/4","siracusaShip 0/8","skillfulShip 0/6","swiftShip 0/23"]
|
"bonds": ["arcaneShip 0/2","kazimierzShip 0/2","preciShip 0/59","sargonShip 0/4","siracusaShip 0/8","skillfulShip 0/6","swiftShip 0/23"]
|
||||||
}
|
}
|
||||||
@@ -821,7 +821,7 @@
|
|||||||
"title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"},
|
"title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"},
|
||||||
"trophies": 2,
|
"trophies": 2,
|
||||||
"reward": 255,
|
"reward": 255,
|
||||||
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":4759668,"gold":124,"itemsEquipped":17,"kills":280,"leaks":70,"lpLost":30,"merges":3,"perfectRounds":4,"refreshes":14},
|
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":4777308,"gold":124,"itemsEquipped":17,"kills":279,"leaks":70,"lpLost":30,"merges":3,"perfectRounds":4,"refreshes":14},
|
||||||
"lineup": [],
|
"lineup": [],
|
||||||
"bonds": ["deputShip 0/44","indomShip 0/16","kazimierzShip 0/2","kjeragShip 0/62","miraShip 0/12","siracusaShip 0/6","skillfulShip 0/69","soloShip 0/28","steadShip 0/55","suntShip 0/16","swiftShip 0/26"]
|
"bonds": ["deputShip 0/44","indomShip 0/16","kazimierzShip 0/2","kjeragShip 0/62","miraShip 0/12","siracusaShip 0/6","skillfulShip 0/69","soloShip 0/28","steadShip 0/55","suntShip 0/16","swiftShip 0/26"]
|
||||||
},
|
},
|
||||||
@@ -849,7 +849,7 @@
|
|||||||
"title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"},
|
"title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"},
|
||||||
"trophies": 2,
|
"trophies": 2,
|
||||||
"reward": 191,
|
"reward": 191,
|
||||||
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1500871,"gold":86,"itemsEquipped":5,"kills":199,"leaks":45,"lpLost":28,"merges":2,"perfectRounds":4,"refreshes":14},
|
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1500871,"gold":86,"itemsEquipped":5,"kills":199,"leaks":45,"lpLost":29,"merges":2,"perfectRounds":4,"refreshes":14},
|
||||||
"lineup": [],
|
"lineup": [],
|
||||||
"bonds": ["deputShip 0/10","miraShip 0/5","siracusaShip 0/84","soloShip 0/22","suntShip 0/6","swiftShip 0/23","yanShip 0/1"]
|
"bonds": ["deputShip 0/10","miraShip 0/5","siracusaShip 0/84","soloShip 0/22","suntShip 0/6","swiftShip 0/23","yanShip 0/1"]
|
||||||
}
|
}
|
||||||
@@ -865,7 +865,7 @@
|
|||||||
"combat": "client",
|
"combat": "client",
|
||||||
"boost": null,
|
"boost": null,
|
||||||
"setup": {"stageId":"act1autochess_m01","bossId":"boss_3","hiddenBossId":null,"factions":["TIMES","ELEMENT","REFLECTION"]},
|
"setup": {"stageId":"act1autochess_m01","bossId":"boss_3","hiddenBossId":null,"factions":["TIMES","ELEMENT","REFLECTION"]},
|
||||||
"end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":129,"bossPoolLeft":0,"virtualMs":133000},
|
"end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":130,"bossPoolLeft":0,"virtualMs":133000},
|
||||||
"rng": {"setup":16,"shop":893,"waves":0,"draft":25,"bots":35892,"meta":29},
|
"rng": {"setup":16,"shop":893,"waves":0,"draft":25,"bots":35892,"meta":29},
|
||||||
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
|
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
|
||||||
"rounds": [
|
"rounds": [
|
||||||
@@ -928,35 +928,35 @@
|
|||||||
{
|
{
|
||||||
"round": 9,
|
"round": 9,
|
||||||
"ai_0": [1,26,12,0,0,4,8,"fc454676",169,"deputShip 1/47, kjeragShip 0/2, preciShip 2/40, sargonShip 1/44, steadShip 1/38",30,30,0,74],
|
"ai_0": [1,26,12,0,0,4,8,"fc454676",169,"deputShip 1/47, kjeragShip 0/2, preciShip 2/40, sargonShip 1/44, steadShip 1/38",30,30,0,74],
|
||||||
"ai_1": [1,44,12,0,0,4,8,"13fccde5",83,"deputShip 2/48, emptyShip 1/4, preciShip 0/4, sargonShip 0/2, siracusaShip 1/31, steadShip 0/4",26,30,4,70],
|
"ai_1": [1,45,12,0,0,4,8,"13fccde5",83,"deputShip 2/48, emptyShip 1/4, preciShip 0/4, sargonShip 0/2, siracusaShip 1/31, steadShip 0/4",26,30,4,70],
|
||||||
"ai_2": [1,28,12,0,0,4,8,"6965081a",70,"sargonShip 0/8, siracusaShip 1/58, swiftShip 1/12, visiShip 1/0, yanShip 0/1",30,30,0,72],
|
"ai_2": [1,28,12,0,0,4,8,"6965081a",70,"sargonShip 0/8, siracusaShip 1/58, swiftShip 1/12, visiShip 1/0, yanShip 0/1",30,30,0,72],
|
||||||
"ai_3": [1,31,13,0,0,4,8,"5f784848",153,"deputShip 1/16, emptyShip 1/12, kjeragShip 1/30, preciShip 1/72, siracusaShip 0/3, swiftShip 1/23, yanShip 0/1",30,30,0,76]
|
"ai_3": [1,31,13,0,0,4,8,"5f784848",153,"deputShip 1/16, emptyShip 1/12, kjeragShip 1/30, preciShip 1/72, siracusaShip 0/3, swiftShip 1/23, yanShip 0/1",30,30,0,76]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"round": 10,
|
"round": 10,
|
||||||
"ai_0": [1,26,12,0,0,5,8,"2b6a123a",243,"deputShip 1/53, kjeragShip 0/2, preciShip 2/72, sargonShip 1/70, steadShip 1/48",29,29,0,86],
|
"ai_0": [1,26,12,0,0,5,8,"2b6a123a",243,"deputShip 1/53, kjeragShip 0/2, preciShip 2/72, sargonShip 1/70, steadShip 1/48",29,29,0,86],
|
||||||
"ai_1": [1,44,12,0,0,5,8,"5bcc6998",105,"deputShip 2/58, emptyShip 1/8, preciShip 0/4, sargonShip 0/2, siracusaShip 1/39, steadShip 0/4",29,29,0,82],
|
"ai_1": [1,45,12,0,0,5,8,"5bcc6998",105,"deputShip 2/58, emptyShip 1/8, preciShip 0/4, sargonShip 0/2, siracusaShip 1/39, steadShip 0/4",29,29,0,82],
|
||||||
"ai_2": [1,28,12,0,0,5,8,"fe139ef2",72,"sargonShip 0/8, siracusaShip 2/60, swiftShip 1/12, visiShip 1/0, yanShip 0/1",29,29,0,83],
|
"ai_2": [1,28,12,0,0,5,8,"fe139ef2",72,"sargonShip 0/8, siracusaShip 2/60, swiftShip 1/12, visiShip 1/0, yanShip 0/1",29,29,0,83],
|
||||||
"ai_3": [1,31,12,0,0,5,8,"9ce217c6",161,"deputShip 1/18, emptyShip 0/12, kjeragShip 2/32, preciShip 1/81, siracusaShip 0/3, swiftShip 1/30, yanShip 0/1",29,29,0,88]
|
"ai_3": [1,31,12,0,0,5,8,"9ce217c6",161,"deputShip 1/18, emptyShip 0/12, kjeragShip 2/32, preciShip 1/81, siracusaShip 0/3, swiftShip 1/30, yanShip 0/1",29,29,0,88]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"round": 11,
|
"round": 11,
|
||||||
"ai_0": [1,26,12,0,0,5,8,"5c1d4d08",291,"deputShip 1/55, kjeragShip 0/2, preciShip 1/88, sargonShip 1/90, steadShip 1/58",28,28,0,99],
|
"ai_0": [1,26,12,0,0,5,8,"5c1d4d08",291,"deputShip 1/55, kjeragShip 0/2, preciShip 1/88, sargonShip 1/90, steadShip 1/58",28,28,0,99],
|
||||||
"ai_1": [1,44,12,0,0,5,8,"28ba083d",133,"deputShip 2/68, emptyShip 1/15, preciShip 0/4, sargonShip 0/2, siracusaShip 1/50, steadShip 0/4",13,28,15,94],
|
"ai_1": [1,45,12,0,0,5,8,"28ba083d",133,"deputShip 2/68, emptyShip 1/15, preciShip 0/4, sargonShip 0/2, siracusaShip 1/50, steadShip 0/4",13,28,15,94],
|
||||||
"ai_2": [1,28,12,0,0,5,8,"8321f307",126,"emptyShip 1/0, sargonShip 0/8, siracusaShip 2/102, swiftShip 1/24, yanShip 0/1",28,28,0,95],
|
"ai_2": [1,28,12,0,0,5,8,"8321f307",126,"emptyShip 1/0, sargonShip 0/8, siracusaShip 2/102, swiftShip 1/24, yanShip 0/1",28,28,0,95],
|
||||||
"ai_3": [1,31,12,0,0,5,7,"5e2bfa81",193,"deputShip 0/18, emptyShip 0/12, kjeragShip 2/66, preciShip 1/92, siracusaShip 0/3, swiftShip 1/35, visiShip 1/0, yanShip 0/1",28,28,0,98]
|
"ai_3": [1,31,12,0,0,5,7,"5e2bfa81",193,"deputShip 0/18, emptyShip 0/12, kjeragShip 2/66, preciShip 1/92, siracusaShip 0/3, swiftShip 1/35, visiShip 1/0, yanShip 0/1",28,28,0,98]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"round": 12,
|
"round": 12,
|
||||||
"ai_0": [1,26,12,0,0,6,8,"934bb983",409,"deputShip 1/57, emptyShip 1/2, kjeragShip 0/2, preciShip 1/138, sargonShip 1/144, steadShip 1/68",46,46,0,111],
|
"ai_0": [1,26,12,0,0,6,8,"934bb983",409,"deputShip 1/57, emptyShip 1/2, kjeragShip 0/2, preciShip 1/138, sargonShip 1/144, steadShip 1/68",46,46,0,111],
|
||||||
"ai_1": [1,44,12,0,0,6,8,"d4b1958d",155,"deputShip 2/78, emptyShip 1/19, preciShip 0/4, sargonShip 0/2, siracusaShip 1/58, steadShip 0/4",20,46,26,104],
|
"ai_1": [1,45,12,0,0,6,8,"d4b1958d",155,"deputShip 2/78, emptyShip 1/19, preciShip 0/4, sargonShip 0/2, siracusaShip 1/58, steadShip 0/4",20,46,26,104],
|
||||||
"ai_2": [1,28,12,0,0,6,8,"696f5ca3",172,"emptyShip 1/0, sargonShip 0/8, siracusaShip 2/148, swiftShip 1/24, yanShip 0/1",46,46,0,106],
|
"ai_2": [1,28,12,0,0,6,8,"696f5ca3",172,"emptyShip 1/0, sargonShip 0/8, siracusaShip 2/148, swiftShip 1/24, yanShip 0/1",46,46,0,106],
|
||||||
"ai_3": [1,31,12,0,0,6,8,"2e3cd509",245,"deputShip 0/18, emptyShip 0/12, kjeragShip 2/106, preciShip 2/99, siracusaShip 0/3, swiftShip 1/40, visiShip 1/0, yanShip 0/1",46,46,0,110]
|
"ai_3": [1,31,12,0,0,6,8,"2e3cd509",245,"deputShip 0/18, emptyShip 0/12, kjeragShip 2/106, preciShip 2/99, siracusaShip 0/3, swiftShip 1/40, visiShip 1/0, yanShip 0/1",46,46,0,110]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"round": 13,
|
"round": 13,
|
||||||
"ai_0": [1,26,12,0,0,6,8,"2df19066",463,"deputShip 1/59, emptyShip 1/4, kjeragShip 0/7, preciShip 2/156, sargonShip 1/166, steadShip 1/78",32,32,0,126],
|
"ai_0": [1,26,12,0,0,6,8,"2df19066",463,"deputShip 1/59, emptyShip 1/4, kjeragShip 0/7, preciShip 2/156, sargonShip 1/166, steadShip 1/78",32,32,0,126],
|
||||||
"ai_1": [1,44,12,0,2,6,8,"9346fb5a",177,"deputShip 1/86, emptyShip 1/21, preciShip 1/6, sargonShip 0/2, siracusaShip 1/64, steadShip 0/4",32,32,0,115],
|
"ai_1": [1,45,12,0,2,6,8,"9346fb5a",177,"deputShip 1/86, emptyShip 1/21, preciShip 1/6, sargonShip 0/2, siracusaShip 1/64, steadShip 0/4",32,32,0,115],
|
||||||
"ai_2": [1,28,12,0,0,6,8,"0236b2a3",238,"emptyShip 1/0, sargonShip 0/8, siracusaShip 2/214, swiftShip 1/24, yanShip 0/1",32,32,0,117],
|
"ai_2": [1,28,12,0,0,6,8,"0236b2a3",238,"emptyShip 1/0, sargonShip 0/8, siracusaShip 2/214, swiftShip 1/24, yanShip 0/1",32,32,0,117],
|
||||||
"ai_3": [1,31,12,0,0,6,7,"ceba29b5",263,"deputShip 0/18, emptyShip 0/12, kjeragShip 1/114, preciShip 2/104, siracusaShip 0/3, swiftShip 1/45, yanShip 0/1",32,32,0,124]
|
"ai_3": [1,31,12,0,0,6,7,"ceba29b5",263,"deputShip 0/18, emptyShip 0/12, kjeragShip 1/114, preciShip 2/104, siracusaShip 0/3, swiftShip 1/45, yanShip 0/1",32,32,0,124]
|
||||||
}
|
}
|
||||||
@@ -972,7 +972,7 @@
|
|||||||
"title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"},
|
"title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"},
|
||||||
"trophies": 3,
|
"trophies": 3,
|
||||||
"reward": 250,
|
"reward": 250,
|
||||||
"stats": {"activatedLayers":485,"bossDamage":46721,"dmgDealt":1702122,"gold":140,"itemsEquipped":25,"kills":327,"leaks":0,"lpLost":0,"merges":5,"perfectRounds":13,"refreshes":21},
|
"stats": {"activatedLayers":485,"bossDamage":46721,"dmgDealt":1699612,"gold":140,"itemsEquipped":25,"kills":328,"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]"],
|
"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"]
|
"bonds": ["deputShip 1/61","emptyShip 1/6","kjeragShip 0/7","preciShip 2/158","sargonShip 1/172","steadShip 1/88"]
|
||||||
},
|
},
|
||||||
@@ -981,12 +981,12 @@
|
|||||||
"victory": true,
|
"victory": true,
|
||||||
"roundsPassed": 14,
|
"roundsPassed": 14,
|
||||||
"eliminatedRound": null,
|
"eliminatedRound": null,
|
||||||
"lp": 44,
|
"lp": 45,
|
||||||
"bandId": "band_sarkazb",
|
"bandId": "band_sarkazb",
|
||||||
"title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"},
|
"title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"},
|
||||||
"trophies": 3,
|
"trophies": 3,
|
||||||
"reward": 250,
|
"reward": 250,
|
||||||
"stats": {"activatedLayers":207,"bossDamage":11652,"dmgDealt":1287691,"gold":129,"itemsEquipped":20,"kills":237,"leaks":46,"lpLost":1,"merges":6,"perfectRounds":9,"refreshes":20},
|
"stats": {"activatedLayers":207,"bossDamage":11652,"dmgDealt":1287691,"gold":129,"itemsEquipped":20,"kills":237,"leaks":46,"lpLost":0,"merges":6,"perfectRounds":9,"refreshes":20},
|
||||||
"lineup": ["2_14_b@12,3","5_10_a@11,6[4_03_e_b+1_02_e_a]","6_01_a@11,7[3_05_e_a]","5_10_a@11,8[5_09_e_a+4_08_e_b]","6_14_a@10,3","4_16_a@10,8[4_11_e_a+4_04_e_b]","6_05_a@9,8[1_05_e_a+5_03_e_a]","2_16_a@9,9[5_02_e_a+4_05_e_b]"],
|
"lineup": ["2_14_b@12,3","5_10_a@11,6[4_03_e_b+1_02_e_a]","6_01_a@11,7[3_05_e_a]","5_10_a@11,8[5_09_e_a+4_08_e_b]","6_14_a@10,3","4_16_a@10,8[4_11_e_a+4_04_e_b]","6_05_a@9,8[1_05_e_a+5_03_e_a]","2_16_a@9,9[5_02_e_a+4_05_e_b]"],
|
||||||
"bonds": ["deputShip 1/96","emptyShip 1/23","preciShip 1/14","sargonShip 0/2","siracusaShip 1/74","steadShip 0/4"]
|
"bonds": ["deputShip 1/96","emptyShip 1/23","preciShip 1/14","sargonShip 0/2","siracusaShip 1/74","steadShip 0/4"]
|
||||||
},
|
},
|
||||||
@@ -997,10 +997,10 @@
|
|||||||
"eliminatedRound": null,
|
"eliminatedRound": null,
|
||||||
"lp": 28,
|
"lp": 28,
|
||||||
"bandId": "band_mlyss",
|
"bandId": "band_mlyss",
|
||||||
"title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"},
|
"title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"},
|
||||||
"trophies": 3,
|
"trophies": 3,
|
||||||
"reward": 250,
|
"reward": 250,
|
||||||
"stats": {"activatedLayers":260,"bossDamage":82751,"dmgDealt":1633721,"gold":127,"itemsEquipped":20,"kills":283,"leaks":0,"lpLost":0,"merges":3,"perfectRounds":13,"refreshes":16},
|
"stats": {"activatedLayers":260,"bossDamage":82751,"dmgDealt":1656521,"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]"],
|
"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/236","swiftShip 1/24","yanShip 0/1"]
|
"bonds": ["sargonShip 0/8","siracusaShip 2/236","swiftShip 1/24","yanShip 0/1"]
|
||||||
},
|
},
|
||||||
@@ -1030,109 +1030,102 @@
|
|||||||
"combat": "client",
|
"combat": "client",
|
||||||
"boost": null,
|
"boost": null,
|
||||||
"setup": {"stageId":"act2autochess_m01","bossId":"boss_1","hiddenBossId":"boss_8","factions":["DOT","INVISIBLE","REFLECTION"]},
|
"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":119300},
|
"end": {"victory":false,"roundsPassed":10,"hiddenReached":false,"hiddenCleared":false,"reason":"eliminated","teamLp":null,"bossPoolLeft":null,"virtualMs":110050},
|
||||||
"rng": {"setup":25,"shop":460,"waves":0,"draft":65,"bots":18743,"meta":57},
|
"rng": {"setup":25,"shop":504,"waves":0,"draft":65,"bots":17663,"meta":57},
|
||||||
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
|
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
|
||||||
"rounds": [
|
"rounds": [
|
||||||
{
|
{
|
||||||
"round": 1,
|
"round": 1,
|
||||||
"ai_0": [1,45,4,0,0,1,2,"cf5de4b4",0,"kazimierzShip 0/2, swiftShip 0/2",5,5,0,4],
|
"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_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]
|
"ai_3": [1,26,4,0,0,1,2,"ba03322a",0,"soloShip 1/0",5,5,0,4]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"round": 2,
|
"round": 2,
|
||||||
"ai_0": [1,45,5,0,0,1,4,"62de86cd",4,"kazimierzShip 0/4, swiftShip 1/4",8,8,0,9],
|
"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_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]
|
"ai_3": [1,26,5,0,0,1,4,"9e98d014",0,"sargonShip 0/2, soloShip 1/0",8,8,0,9]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"round": 3,
|
"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_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_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]
|
"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,
|
"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_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_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,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_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,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_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,
|
"round": 5,
|
||||||
"ai_0": [1,35,9,0,0,3,8,"17121de7",8,"arcaneShip 0/8, kazimierzShip 0/4, preciShip 0/8, swiftShip 1/8",5,17,12,32],
|
"ai_0": [1,35,9,0,0,3,8,"17121de7",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_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,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_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,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_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,
|
"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_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,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",10,14,4,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,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_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,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_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,
|
"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",8,21,13,52],
|
"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,6,11,0,0,4,8,"8016e17e",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",4,21,17,51],
|
"ai_1": [1,4,10,0,0,4,8,"f1d0d89f",38,"arcaneShip 0/2, deputShip 1/2, kazimierzShip 1/15, kjeragShip 0/2, preciShip 0/2, sargonShip 0/4, siracusaShip 0/6, soloShip 1/2, steadShip 0/2, swiftShip 1/19",10,21,11,50],
|
||||||
"ai_2": [1,21,10,0,2,4,8,"4889cc01",78,"deputShip 1/16, egirShip 0/4, kazimierzShip 0/4, kjeragShip 0/12, raidShip 0/4, skillfulShip 0/6, soloShip 1/8, steadShip 2/33, swiftShip 1/21, victoriaShip 0/12, yanShip 0/4",21,21,0,51],
|
"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,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_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,
|
"round": 8,
|
||||||
"ai_0": [1,19,11,0,0,4,8,"78106ce5",93,"arcaneShip 2/8, kazimierzShip 1/22, kjeragShip 0/5, preciShip 2/34, suntShip 1/0, swiftShip 1/29",3,9,6,63],
|
"ai_0": [1,20,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",4,9,5,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_1": [0,0,11,0,0,4,0,"811c9dc5",0,"arcaneShip 0/2, deputShip 0/6, kazimierzShip 0/27, kjeragShip 0/2, preciShip 0/2, sargonShip 0/8, siracusaShip 0/6, skillfulShip 0/4, soloShip 0/2, steadShip 0/2, swiftShip 0/34",0,9,9,62],
|
||||||
"ai_2": [1,18,13,0,0,4,8,"eff86771",113,"deputShip 1/20, egirShip 0/6, kazimierzShip 0/4, kjeragShip 0/12, lateranoShip 0/2, raidShip 0/6, skillfulShip 0/6, soloShip 1/12, steadShip 2/46, swiftShip 1/35, victoriaShip 0/14, yanShip 0/4",6,9,3,64],
|
"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/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_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,
|
"round": 9,
|
||||||
"ai_0": [1,18,12,0,1,4,8,"a359f7ee",121,"arcaneShip 1/8, indomShip 1/2, kazimierzShip 1/34, kjeragShip 0/5, preciShip 2/43, suntShip 1/0, swiftShip 1/34",6,19,13,76],
|
"ai_0": [1,10,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, 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_1": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/2, deputShip 0/6, kazimierzShip 0/27, 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,18,12,0,1,4,8,"8df12b45",145,"deputShip 0/22, egirShip 0/6, kazimierzShip 0/4, kjeragShip 0/12, lateranoShip 0/2, preciShip 0/2, raidShip 0/6, skillfulShip 1/8, soloShip 1/12, steadShip 1/51, suntShip 1/0, swiftShip 1/49, victoriaShip 1/25, yanShip 0/13",19,19,0,76],
|
"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/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_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,
|
"round": 10,
|
||||||
"ai_0": [1,18,13,0,0,5,8,"1c690635",145,"arcaneShip 1/8, indomShip 1/4, kazimierzShip 1/42, kjeragShip 0/5, preciShip 2/52, suntShip 1/0, swiftShip 1/39",17,25,8,88],
|
"ai_0": [1,6,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, 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_1": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/2, deputShip 0/6, kazimierzShip 0/27, 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,18,13,0,0,5,8,"0e4a12ab",175,"deputShip 0/24, egirShip 0/6, kazimierzShip 0/4, kjeragShip 0/12, lateranoShip 0/4, preciShip 0/2, raidShip 0/6, skillfulShip 1/12, soloShip 1/12, steadShip 1/57, suntShip 1/0, swiftShip 1/63, victoriaShip 1/31, yanShip 0/15",25,25,0,89],
|
"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/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_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,
|
"round": 11,
|
||||||
"ai_0": [1,8,12,0,1,5,8,"88de4e65",205,"arcaneShip 1/8, indomShip 0/4, kazimierzShip 1/64, kjeragShip 0/5, preciShip 2/70, raidShip 1/12, suntShip 1/0, swiftShip 1/51",13,25,12,100],
|
"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",7,25,18,99],
|
||||||
"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_1": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/2, deputShip 0/6, kazimierzShip 0/27, 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,8,12,0,0,5,8,"81827b1b",175,"deputShip 0/24, egirShip 0/6, kazimierzShip 0/4, kjeragShip 0/14, lateranoShip 0/4, preciShip 0/4, raidShip 0/6, skillfulShip 1/16, soloShip 1/12, steadShip 0/59, suntShip 1/0, swiftShip 1/77, victoriaShip 1/35, yanShip 1/35",4,25,21,100],
|
"ai_2": [0,0,14,0,0,5,0,"811c9dc5",0,"deputShip 0/34, egirShip 0/4, indomShip 0/30, 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/58, visiShip 0/6, yanShip 0/12",11,25,14,102],
|
||||||
"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_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": 12,
|
|
||||||
"ai_0": [0,0,13,0,0,6,0,"811c9dc5",0,"arcaneShip 0/8, indomShip 0/4, kazimierzShip 0/80, kjeragShip 0/5, preciShip 0/79, raidShip 0/24, swiftShip 0/56",9,47,38,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/16, lateranoShip 0/4, preciShip 0/6, raidShip 0/6, skillfulShip 0/18, soloShip 0/12, steadShip 0/59, swiftShip 0/91, victoriaShip 0/43, yanShip 0/37",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]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"players": {
|
"players": {
|
||||||
"ai_0": {
|
"ai_0": {
|
||||||
"alive": false,
|
"alive": false,
|
||||||
"victory": false,
|
"victory": false,
|
||||||
"roundsPassed": 11,
|
"roundsPassed": 10,
|
||||||
"eliminatedRound": 12,
|
"eliminatedRound": 11,
|
||||||
"lp": 0,
|
"lp": 0,
|
||||||
"bandId": "band_sarkazb",
|
"bandId": "band_sarkazb",
|
||||||
"title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"},
|
"title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"},
|
||||||
"trophies": 2,
|
"trophies": 2,
|
||||||
"reward": 255,
|
"reward": 213,
|
||||||
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2923937,"gold":114,"itemsEquipped":8,"kills":107,"leaks":102,"lpLost":47,"merges":5,"perfectRounds":5,"refreshes":9},
|
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1538413,"gold":99,"itemsEquipped":11,"kills":86,"leaks":76,"lpLost":49,"merges":2,"perfectRounds":4,"refreshes":12},
|
||||||
"lineup": [],
|
"lineup": [],
|
||||||
"bonds": ["arcaneShip 0/8","indomShip 0/4","kazimierzShip 0/80","kjeragShip 0/5","preciShip 0/79","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": {
|
"ai_1": {
|
||||||
"alive": false,
|
"alive": false,
|
||||||
@@ -1144,23 +1137,23 @@
|
|||||||
"title": null,
|
"title": null,
|
||||||
"trophies": 1,
|
"trophies": 1,
|
||||||
"reward": 149,
|
"reward": 149,
|
||||||
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":649468,"gold":62,"itemsEquipped":3,"kills":55,"leaks":43,"lpLost":29,"merges":1,"perfectRounds":3,"refreshes":5},
|
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":822006,"gold":62,"itemsEquipped":5,"kills":63,"leaks":34,"lpLost":31,"merges":2,"perfectRounds":3,"refreshes":5},
|
||||||
"lineup": [],
|
"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/27","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": {
|
"ai_2": {
|
||||||
"alive": false,
|
"alive": false,
|
||||||
"victory": false,
|
"victory": false,
|
||||||
"roundsPassed": 11,
|
"roundsPassed": 10,
|
||||||
"eliminatedRound": 12,
|
"eliminatedRound": 11,
|
||||||
"lp": 0,
|
"lp": 0,
|
||||||
"bandId": "band_bldsk",
|
"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,
|
"trophies": 2,
|
||||||
"reward": 255,
|
"reward": 213,
|
||||||
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":4317948,"gold":113,"itemsEquipped":11,"kills":172,"leaks":72,"lpLost":30,"merges":3,"perfectRounds":6,"refreshes":9},
|
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":3090544,"gold":102,"itemsEquipped":10,"kills":143,"leaks":33,"lpLost":29,"merges":2,"perfectRounds":5,"refreshes":14},
|
||||||
"lineup": [],
|
"lineup": [],
|
||||||
"bonds": ["deputShip 0/26","egirShip 0/6","kazimierzShip 0/4","kjeragShip 0/16","lateranoShip 0/4","preciShip 0/6","raidShip 0/6","skillfulShip 0/18","soloShip 0/12","steadShip 0/59","swiftShip 0/91","victoriaShip 0/43","yanShip 0/37"]
|
"bonds": ["deputShip 0/34","egirShip 0/4","indomShip 0/30","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/58","visiShip 0/6","yanShip 0/12"]
|
||||||
},
|
},
|
||||||
"ai_3": {
|
"ai_3": {
|
||||||
"alive": false,
|
"alive": false,
|
||||||
@@ -1169,12 +1162,12 @@
|
|||||||
"eliminatedRound": 8,
|
"eliminatedRound": 8,
|
||||||
"lp": 0,
|
"lp": 0,
|
||||||
"bandId": "band_cathy",
|
"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,
|
"trophies": 1,
|
||||||
"reward": 149,
|
"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": [],
|
"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",
|
"combat": "server",
|
||||||
"boost": null,
|
"boost": null,
|
||||||
"setup": {"stageId":"act1autochess_m01","bossId":"boss_1","hiddenBossId":"boss_9","factions":["FLY","DOT","REFLECTION"]},
|
"setup": {"stageId":"act1autochess_m01","bossId":"boss_1","hiddenBossId":"boss_9","factions":["FLY","DOT","REFLECTION"]},
|
||||||
"end": {"victory":false,"roundsPassed":12,"hiddenReached":false,"hiddenCleared":false,"reason":"eliminated","teamLp":null,"bossPoolLeft":null,"virtualMs":115150},
|
"end": {"victory":false,"roundsPassed":12,"hiddenReached":false,"hiddenCleared":false,"reason":"eliminated","teamLp":null,"bossPoolLeft":null,"virtualMs":114800},
|
||||||
"rng": {"setup":25,"shop":411,"waves":0,"draft":34,"bots":17567,"meta":104},
|
"rng": {"setup":25,"shop":383,"waves":0,"draft":34,"bots":16823,"meta":105},
|
||||||
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
|
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
|
||||||
"rounds": [
|
"rounds": [
|
||||||
{
|
{
|
||||||
@@ -1230,32 +1223,32 @@
|
|||||||
{
|
{
|
||||||
"round": 8,
|
"round": 8,
|
||||||
"ai_0": [1,19,11,0,0,4,8,"7c223b06",60,"arcaneShip 1/6, emptyShip 1/0, kjeragShip 0/7, preciShip 1/7, siracusaShip 1/30, soloShip 1/11, steadShip 0/2, swiftShip 1/6",23,23,0,64],
|
"ai_0": [1,19,11,0,0,4,8,"7c223b06",60,"arcaneShip 1/6, emptyShip 1/0, kjeragShip 0/7, preciShip 1/7, siracusaShip 1/30, 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,
|
"round": 9,
|
||||||
"ai_0": [1,19,12,0,1,4,8,"588666d3",72,"arcaneShip 1/12, emptyShip 1/0, indomShip 0/8, kjeragShip 0/7, preciShip 2/7, siracusaShip 1/36, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",16,16,0,78],
|
"ai_0": [1,19,12,0,1,4,8,"588666d3",72,"arcaneShip 1/12, emptyShip 1/0, indomShip 0/8, kjeragShip 0/7, preciShip 2/7, siracusaShip 1/36, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",16,16,0,78],
|
||||||
"ai_1": [1,17,12,0,0,4,8,"0f3cf873",67,"arcaneShip 1/14, indomShip 1/10, kjeragShip 0/4, preciShip 0/2, siracusaShip 1/32, soloShip 1/0, steadShip 1/11, victoriaShip 0/8, visiShip 0/8",14,15,1,73]
|
"ai_1": [1,15,12,0,0,4,8,"0f3cf873",67,"arcaneShip 1/14, indomShip 1/10, kjeragShip 0/4, preciShip 0/2, siracusaShip 1/32, soloShip 1/0, steadShip 1/11, victoriaShip 0/8, visiShip 0/8",14,15,1,73]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"round": 10,
|
"round": 10,
|
||||||
"ai_0": [1,17,13,0,0,5,8,"bb29d023",80,"arcaneShip 1/16, emptyShip 1/0, indomShip 0/8, kjeragShip 0/7, preciShip 2/7, siracusaShip 1/40, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",22,24,2,91],
|
"ai_0": [1,17,13,0,0,5,8,"bb29d023",80,"arcaneShip 1/16, emptyShip 1/0, indomShip 0/8, kjeragShip 0/7, preciShip 2/7, siracusaShip 1/40, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",22,24,2,91],
|
||||||
"ai_1": [1,16,12,0,0,5,8,"34e3de0f",83,"arcaneShip 1/14, indomShip 1/20, kjeragShip 0/8, preciShip 0/2, siracusaShip 1/32, soloShip 1/0, steadShip 1/17, suntShip 1/0, victoriaShip 0/8, visiShip 0/8",22,23,1,85]
|
"ai_1": [1,14,12,0,0,5,8,"34e3de0f",83,"arcaneShip 1/14, indomShip 1/20, kjeragShip 0/8, preciShip 0/2, siracusaShip 1/32, soloShip 1/0, steadShip 1/17, suntShip 1/0, victoriaShip 0/8, visiShip 0/8",22,23,1,85]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"round": 11,
|
"round": 11,
|
||||||
"ai_0": [1,17,12,0,0,5,8,"f0ff6294",86,"arcaneShip 1/22, emptyShip 1/0, indomShip 0/8, kjeragShip 0/7, preciShip 1/7, siracusaShip 2/46, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 0/6, victoriaShip 0/8, visiShip 0/8",23,23,0,104],
|
"ai_0": [1,17,12,0,0,5,8,"f0ff6294",86,"arcaneShip 1/22, emptyShip 1/0, indomShip 0/8, kjeragShip 0/7, preciShip 1/7, siracusaShip 2/46, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 0/6, victoriaShip 0/8, visiShip 0/8",23,23,0,104],
|
||||||
"ai_1": [1,16,12,0,0,5,8,"f020d91c",137,"arcaneShip 1/20, indomShip 1/40, kjeragShip 0/8, preciShip 0/2, siracusaShip 1/58, soloShip 1/0, steadShip 1/19, suntShip 1/0, victoriaShip 0/8, visiShip 0/8",17,23,6,97]
|
"ai_1": [1,8,12,0,0,5,8,"f020d91c",137,"arcaneShip 1/20, indomShip 1/40, kjeragShip 0/8, preciShip 0/2, siracusaShip 1/58, soloShip 1/0, steadShip 1/19, suntShip 1/0, victoriaShip 0/8, visiShip 0/8",17,23,6,97]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"round": 12,
|
"round": 12,
|
||||||
"ai_0": [1,7,12,0,0,6,8,"09ffe6d0",86,"arcaneShip 1/22, emptyShip 1/0, indomShip 0/8, kjeragShip 0/7, preciShip 2/7, siracusaShip 1/46, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 0/6, victoriaShip 0/8, visiShip 0/8",35,47,12,116],
|
"ai_0": [1,7,12,0,0,6,8,"09ffe6d0",86,"arcaneShip 1/22, emptyShip 1/0, indomShip 0/8, kjeragShip 0/7, preciShip 2/7, siracusaShip 1/46, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 0/6, victoriaShip 0/8, visiShip 0/8",35,47,12,116],
|
||||||
"ai_1": [1,6,12,0,0,6,8,"d9bd3d27",160,"arcaneShip 1/20, indomShip 1/60, kjeragShip 0/8, preciShip 0/2, siracusaShip 1/58, soloShip 1/0, steadShip 1/22, suntShip 1/0, victoriaShip 0/8, visiShip 0/8",17,46,29,109]
|
"ai_1": [0,0,12,0,0,6,0,"811c9dc5",0,"arcaneShip 0/20, indomShip 0/60, kjeragShip 0/8, preciShip 0/2, siracusaShip 0/58, steadShip 0/22, victoriaShip 0/8, visiShip 0/8",17,46,29,109]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"round": 13,
|
"round": 13,
|
||||||
"ai_0": [0,0,12,0,0,6,0,"811c9dc5",0,"arcaneShip 0/36, indomShip 0/8, kjeragShip 0/7, miraShip 0/3, preciShip 0/9, siracusaShip 0/58, soloShip 0/11, steadShip 0/2, swiftShip 0/6, victoriaShip 0/8, visiShip 0/8, yanShip 0/6",21,39,18,127],
|
"ai_0": [0,0,12,0,0,6,0,"811c9dc5",0,"arcaneShip 0/36, indomShip 0/8, kjeragShip 0/7, miraShip 0/3, preciShip 0/9, siracusaShip 0/58, soloShip 0/11, steadShip 0/2, swiftShip 0/6, victoriaShip 0/8, visiShip 0/8, yanShip 0/6",21,39,18,128],
|
||||||
"ai_1": [0,0,12,0,0,6,0,"811c9dc5",0,"arcaneShip 0/26, indomShip 0/60, kjeragShip 0/8, miraShip 0/4, preciShip 0/2, siracusaShip 0/84, steadShip 0/23, victoriaShip 0/8, visiShip 0/8",19,39,20,120]
|
"ai_1": [0,0,0,0,0,6,0,"811c9dc5",0,"arcaneShip 0/20, indomShip 0/60, kjeragShip 0/8, preciShip 0/2, siracusaShip 0/58, steadShip 0/22, victoriaShip 0/8, visiShip 0/8",null,null,null,109]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"players": {
|
"players": {
|
||||||
@@ -1269,23 +1262,23 @@
|
|||||||
"title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"},
|
"title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"},
|
||||||
"trophies": 3,
|
"trophies": 3,
|
||||||
"reward": 280,
|
"reward": 280,
|
||||||
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":3037907,"gold":127,"itemsEquipped":14,"kills":238,"leaks":49,"lpLost":33,"merges":3,"perfectRounds":8,"refreshes":17},
|
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2825652,"gold":128,"itemsEquipped":14,"kills":230,"leaks":49,"lpLost":33,"merges":3,"perfectRounds":8,"refreshes":18},
|
||||||
"lineup": [],
|
"lineup": [],
|
||||||
"bonds": ["arcaneShip 0/36","indomShip 0/8","kjeragShip 0/7","miraShip 0/3","preciShip 0/9","siracusaShip 0/58","soloShip 0/11","steadShip 0/2","swiftShip 0/6","victoriaShip 0/8","visiShip 0/8","yanShip 0/6"]
|
"bonds": ["arcaneShip 0/36","indomShip 0/8","kjeragShip 0/7","miraShip 0/3","preciShip 0/9","siracusaShip 0/58","soloShip 0/11","steadShip 0/2","swiftShip 0/6","victoriaShip 0/8","visiShip 0/8","yanShip 0/6"]
|
||||||
},
|
},
|
||||||
"ai_1": {
|
"ai_1": {
|
||||||
"alive": false,
|
"alive": false,
|
||||||
"victory": false,
|
"victory": false,
|
||||||
"roundsPassed": 12,
|
"roundsPassed": 11,
|
||||||
"eliminatedRound": 13,
|
"eliminatedRound": 12,
|
||||||
"lp": 0,
|
"lp": 0,
|
||||||
"bandId": "band_malkie",
|
"bandId": "band_malkie",
|
||||||
"title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"},
|
"title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"},
|
||||||
"trophies": 3,
|
"trophies": 2,
|
||||||
"reward": 280,
|
"reward": 240,
|
||||||
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2410241,"gold":120,"itemsEquipped":16,"kills":196,"leaks":73,"lpLost":32,"merges":2,"perfectRounds":6,"refreshes":18},
|
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1887768,"gold":109,"itemsEquipped":16,"kills":176,"leaks":53,"lpLost":30,"merges":2,"perfectRounds":6,"refreshes":14},
|
||||||
"lineup": [],
|
"lineup": [],
|
||||||
"bonds": ["arcaneShip 0/26","indomShip 0/60","kjeragShip 0/8","miraShip 0/4","preciShip 0/2","siracusaShip 0/84","steadShip 0/23","victoriaShip 0/8","visiShip 0/8"]
|
"bonds": ["arcaneShip 0/20","indomShip 0/60","kjeragShip 0/8","preciShip 0/2","siracusaShip 0/58","steadShip 0/22","victoriaShip 0/8","visiShip 0/8"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1300,7 +1293,7 @@
|
|||||||
"boost": {"lp":400,"layers":300},
|
"boost": {"lp":400,"layers":300},
|
||||||
"setup": {"stageId":"act1autochess_m02","bossId":"boss_5","hiddenBossId":"boss_10","factions":["TIMES","ELEMENT","DOT"]},
|
"setup": {"stageId":"act1autochess_m02","bossId":"boss_5","hiddenBossId":"boss_10","factions":["TIMES","ELEMENT","DOT"]},
|
||||||
"end": {"victory":true,"roundsPassed":15,"hiddenReached":true,"hiddenCleared":true,"reason":"victory","teamLp":399,"bossPoolLeft":0,"virtualMs":113700},
|
"end": {"victory":true,"roundsPassed":15,"hiddenReached":true,"hiddenCleared":true,"reason":"victory","teamLp":399,"bossPoolLeft":0,"virtualMs":113700},
|
||||||
"rng": {"setup":25,"shop":346,"waves":0,"draft":42,"bots":10350,"meta":3},
|
"rng": {"setup":25,"shop":356,"waves":0,"draft":42,"bots":10350,"meta":3},
|
||||||
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
|
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
|
||||||
"rounds": [
|
"rounds": [
|
||||||
{
|
{
|
||||||
@@ -1353,7 +1346,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"round": 13,
|
"round": 13,
|
||||||
"ai_0": [1,399,12,0,2,6,8,"eb5e835a",1944,"arcaneShip 2/360, deputShip 0/300, egirShip 0/300, emptyShip 0/300, indomShip 0/308, investShip 0/300, kazimierzShip 0/308, kjeragShip 0/300, lateranoShip 1/310, maniShip 0/300, miraShip 0/300, preciShip 0/302, raidShip 0/300, sargonShip 0/300, siracusaShip 1/332, skillfulShip 1/335, soloShip 1/307, steadShip 0/300, suntShip 1/300, swiftShip 0/316, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",35,35,0,151]
|
"ai_0": [1,399,12,0,2,6,8,"5091cd4e",1944,"arcaneShip 2/360, deputShip 0/300, egirShip 0/300, emptyShip 0/300, indomShip 0/308, investShip 0/300, kazimierzShip 0/308, kjeragShip 0/300, lateranoShip 1/310, maniShip 0/300, miraShip 0/300, preciShip 0/302, raidShip 0/300, sargonShip 0/300, siracusaShip 1/332, skillfulShip 1/335, soloShip 1/307, steadShip 0/300, suntShip 1/300, swiftShip 0/316, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",35,35,0,150]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"players": {
|
"players": {
|
||||||
@@ -1367,9 +1360,9 @@
|
|||||||
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
|
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
|
||||||
"trophies": 0,
|
"trophies": 0,
|
||||||
"reward": 425,
|
"reward": 425,
|
||||||
"stats": {"activatedLayers":2269,"bossDamage":1145000,"dmgDealt":3047851,"gold":176,"itemsEquipped":26,"kills":366,"leaks":1,"lpLost":1,"merges":5,"perfectRounds":12,"refreshes":34},
|
"stats": {"activatedLayers":2283,"bossDamage":1145000,"dmgDealt":3047851,"gold":175,"itemsEquipped":26,"kills":366,"leaks":1,"lpLost":1,"merges":6,"perfectRounds":12,"refreshes":35},
|
||||||
"lineup": ["4_02_b@12,7[3_08_e_a+6_03_e_a]","6_01_a@12,8[3_03_e_a+3_02_e_a]","6_16_a@11,5[1_05_e_b+1_02_e_b]","3_18_a@11,8[4_11_e_a+4_05_e_b]","4_10_a@10,4[4_07_e_a+3_03_e_b]","6_05_a@10,5[1_02_e_a]","4_01_b@10,6[4_06_e_a+4_05_e_a]","1_08_b@9,9[4_10_e_a]"],
|
"lineup": ["4_02_b@12,7[3_08_e_a+3_03_e_b]","6_01_a@12,8[3_02_e_a]","6_16_a@11,5[1_05_e_b+3_03_e_a]","3_18_a@11,8[4_11_e_a+4_05_e_b]","4_10_b@10,4[4_07_e_a+1_02_e_b]","6_05_a@10,5","4_01_b@10,6[6_03_e_a+4_06_e_a]","1_08_b@9,9"],
|
||||||
"bonds": ["arcaneShip 2/389","deputShip 0/300","egirShip 0/300","emptyShip 0/300","indomShip 0/308","investShip 0/300","kazimierzShip 0/308","kjeragShip 0/300","lateranoShip 1/310","maniShip 0/300","miraShip 0/300","preciShip 1/302","raidShip 0/300","sargonShip 0/300","siracusaShip 1/345","skillfulShip 0/335","soloShip 1/307","steadShip 0/300","suntShip 1/300","swiftShip 1/316","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"]
|
"bonds": ["arcaneShip 2/395","deputShip 0/300","egirShip 0/300","emptyShip 0/300","indomShip 0/308","investShip 0/300","kazimierzShip 0/308","kjeragShip 0/300","lateranoShip 1/310","maniShip 0/300","miraShip 0/300","preciShip 1/302","raidShip 0/300","sargonShip 0/300","siracusaShip 1/353","skillfulShip 0/343","soloShip 1/307","steadShip 0/300","suntShip 1/300","swiftShip 1/316","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1383,8 +1376,8 @@
|
|||||||
"combat": "client",
|
"combat": "client",
|
||||||
"boost": {"lp":400,"layers":300},
|
"boost": {"lp":400,"layers":300},
|
||||||
"setup": {"stageId":"act2autochess_m04","bossId":"boss_5","hiddenBossId":"boss_9","factions":["FLY","ELEMENT","INVISIBLE"]},
|
"setup": {"stageId":"act2autochess_m04","bossId":"boss_5","hiddenBossId":"boss_9","factions":["FLY","ELEMENT","INVISIBLE"]},
|
||||||
"end": {"victory":true,"roundsPassed":15,"hiddenReached":true,"hiddenCleared":true,"reason":"victory","teamLp":800,"bossPoolLeft":0,"virtualMs":124650},
|
"end": {"victory":true,"roundsPassed":15,"hiddenReached":true,"hiddenCleared":true,"reason":"victory","teamLp":800,"bossPoolLeft":0,"virtualMs":126150},
|
||||||
"rng": {"setup":25,"shop":658,"waves":0,"draft":61,"bots":22084,"meta":147},
|
"rng": {"setup":25,"shop":674,"waves":0,"draft":61,"bots":22077,"meta":140},
|
||||||
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
|
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
|
||||||
"rounds": [
|
"rounds": [
|
||||||
{
|
{
|
||||||
@@ -1435,22 +1428,22 @@
|
|||||||
{
|
{
|
||||||
"round": 10,
|
"round": 10,
|
||||||
"ai_0": [1,400,12,0,0,5,8,"5e407c98",2262,"arcaneShip 2/320, deputShip 0/304, egirShip 1/334, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 1/314, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/320, raidShip 1/338, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/304, steadShip 1/332, suntShip 0/300, swiftShip 0/306, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",12,12,0,93],
|
"ai_0": [1,400,12,0,0,5,8,"5e407c98",2262,"arcaneShip 2/320, deputShip 0/304, egirShip 1/334, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 1/314, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/320, raidShip 1/338, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/304, steadShip 1/332, suntShip 0/300, swiftShip 0/306, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",12,12,0,93],
|
||||||
"ai_1": [1,400,15,0,0,5,8,"fa4268ad",2207,"arcaneShip 0/302, deputShip 1/318, egirShip 0/300, emptyShip 0/300, indomShip 1/308, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 0/309, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 1/304, soloShip 1/316, steadShip 1/343, suntShip 1/302, swiftShip 0/300, victoriaShip 1/316, visiShip 0/300, yanShip 0/310",12,12,0,99]
|
"ai_1": [1,400,15,0,0,5,8,"1cb9c2e4",1583,"arcaneShip 0/302, deputShip 1/318, egirShip 0/300, emptyShip 0/300, indomShip 1/308, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 0/309, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 1/304, soloShip 0/314, steadShip 1/342, suntShip 0/300, swiftShip 0/300, victoriaShip 1/311, visiShip 0/300, yanShip 0/305",12,12,0,97]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"round": 11,
|
"round": 11,
|
||||||
"ai_0": [1,400,12,0,2,5,8,"e2bf7b5d",2634,"arcaneShip 1/323, deputShip 0/304, egirShip 1/345, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 1/322, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/329, raidShip 1/360, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/304, steadShip 1/336, suntShip 0/300, swiftShip 1/315, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",34,34,0,105],
|
"ai_0": [1,400,12,0,2,5,8,"cfd3ac7b",2321,"arcaneShip 1/323, deputShip 0/304, egirShip 1/352, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 1/322, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/324, raidShip 1/360, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/304, steadShip 1/336, suntShip 0/300, swiftShip 0/310, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",34,34,0,105],
|
||||||
"ai_1": [1,400,12,0,1,5,8,"5dc25201",2235,"arcaneShip 0/302, deputShip 1/320, egirShip 0/300, emptyShip 0/300, indomShip 1/310, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/313, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/304, soloShip 1/318, steadShip 1/350, suntShip 1/304, swiftShip 0/300, victoriaShip 1/320, visiShip 0/300, yanShip 0/310",34,34,0,111]
|
"ai_1": [1,400,12,0,1,6,8,"b6bd931e",2209,"arcaneShip 0/302, deputShip 1/320, egirShip 0/300, emptyShip 0/300, indomShip 1/312, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 0/309, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 1/306, soloShip 0/314, steadShip 1/349, suntShip 1/302, swiftShip 0/300, victoriaShip 1/316, visiShip 1/304, yanShip 0/305",34,34,0,120]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"round": 12,
|
"round": 12,
|
||||||
"ai_0": [1,400,14,0,0,6,8,"e09e9634",2679,"arcaneShip 1/326, deputShip 0/304, egirShip 1/351, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 1/330, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/342, raidShip 1/370, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/304, steadShip 1/336, suntShip 0/300, swiftShip 1/320, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",6,14,8,119],
|
"ai_0": [1,400,14,0,0,6,8,"e7a322b8",2348,"arcaneShip 1/323, deputShip 0/304, egirShip 1/353, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 1/330, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/332, raidShip 1/370, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/304, steadShip 1/336, suntShip 0/300, swiftShip 0/310, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",6,14,8,119],
|
||||||
"ai_1": [1,400,13,0,2,6,8,"13f2f54b",2259,"arcaneShip 0/302, deputShip 1/322, egirShip 0/300, emptyShip 0/300, indomShip 1/312, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/317, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/304, soloShip 1/320, steadShip 1/358, suntShip 1/306, swiftShip 0/300, victoriaShip 1/324, visiShip 0/300, yanShip 0/310",15,15,0,124]
|
"ai_1": [1,400,13,0,1,6,8,"58fdf70d",2235,"arcaneShip 0/302, deputShip 1/322, egirShip 0/300, emptyShip 0/300, indomShip 2/314, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 0/309, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 1/308, soloShip 0/314, steadShip 1/357, suntShip 1/304, swiftShip 0/300, victoriaShip 1/324, visiShip 1/306, yanShip 0/310",15,15,0,134]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"round": 13,
|
"round": 13,
|
||||||
"ai_0": [1,400,12,0,0,6,8,"871ba326",3034,"arcaneShip 1/333, deputShip 0/304, egirShip 1/369, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 1/333, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/354, raidShip 1/376, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/304, steadShip 1/340, suntShip 1/300, swiftShip 1/325, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",41,41,0,130],
|
"ai_0": [1,400,12,0,0,6,8,"feef54ce",2706,"arcaneShip 2/330, deputShip 0/304, egirShip 1/368, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 1/334, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/340, raidShip 1/390, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/304, steadShip 1/340, suntShip 1/300, swiftShip 0/310, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",41,41,0,133],
|
||||||
"ai_1": [1,400,14,0,2,6,8,"0e053b02",2595,"arcaneShip 0/302, deputShip 1/324, egirShip 0/300, emptyShip 0/300, indomShip 1/318, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/319, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/304, soloShip 1/322, steadShip 1/366, suntShip 2/308, swiftShip 0/300, victoriaShip 1/332, visiShip 1/306, yanShip 0/310",41,41,0,170]
|
"ai_1": [1,400,13,0,2,6,8,"6a6f946a",1962,"arcaneShip 0/302, deputShip 1/324, egirShip 0/300, emptyShip 0/300, indomShip 2/322, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 0/309, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 1/310, soloShip 0/314, steadShip 1/366, suntShip 1/306, swiftShip 0/300, victoriaShip 1/334, visiShip 0/306, yanShip 0/310",36,41,5,150]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"players": {
|
"players": {
|
||||||
@@ -1461,12 +1454,12 @@
|
|||||||
"eliminatedRound": null,
|
"eliminatedRound": null,
|
||||||
"lp": 400,
|
"lp": 400,
|
||||||
"bandId": "band_sarkazb",
|
"bandId": "band_sarkazb",
|
||||||
"title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"},
|
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
|
||||||
"trophies": 8,
|
"trophies": 8,
|
||||||
"reward": 425,
|
"reward": 425,
|
||||||
"stats": {"activatedLayers":3054,"bossDamage":2388329,"dmgDealt":11201551,"gold":156,"itemsEquipped":15,"kills":287,"leaks":8,"lpLost":0,"merges":6,"perfectRounds":12,"refreshes":28},
|
"stats": {"activatedLayers":2986,"bossDamage":4110836,"dmgDealt":14169113,"gold":158,"itemsEquipped":18,"kills":295,"leaks":8,"lpLost":0,"merges":7,"perfectRounds":12,"refreshes":25},
|
||||||
"lineup": ["4_09_b@11,5","4_14_a@11,8","5_07_a@10,4[1_01_e_a]","2_18_a@10,5","1_19_b@10,8[4_04_e_b+1_02_e_b]","3_09_a@9,7[2_01_e_a+3_08_e_a]","4_20_a@9,8","3_05_a@9,9[4_07_e_a+5_02_e_a]"],
|
"lineup": ["4_09_b@11,5[4_11_e_a+6_09_e_a]","6_11_a@11,8","5_07_a@10,4[1_01_e_b+1_02_e_b]","1_19_b@10,8[5_02_e_a+4_04_e_a]","3_09_b@9,6[3_08_e_a+4_10_e_a]","6_05_a@9,7","4_20_a@9,8[2_01_e_a]","3_05_a@9,9[4_07_e_a+4_06_e_a]"],
|
||||||
"bonds": ["arcaneShip 1/333","deputShip 0/304","egirShip 1/369","emptyShip 0/300","indomShip 0/300","investShip 0/300","kazimierzShip 1/333","kjeragShip 0/302","lateranoShip 0/300","maniShip 0/300","miraShip 0/300","preciShip 1/364","raidShip 1/376","sargonShip 0/300","siracusaShip 0/300","skillfulShip 0/300","soloShip 1/304","steadShip 1/340","suntShip 1/300","swiftShip 1/335","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"]
|
"bonds": ["arcaneShip 1/330","deputShip 0/304","egirShip 1/368","emptyShip 0/300","indomShip 0/300","investShip 0/300","kazimierzShip 1/334","kjeragShip 0/302","lateranoShip 0/300","maniShip 1/300","miraShip 0/300","preciShip 1/345","raidShip 1/390","sargonShip 0/300","siracusaShip 0/300","skillfulShip 0/300","soloShip 1/304","steadShip 0/340","suntShip 1/300","swiftShip 1/315","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"]
|
||||||
},
|
},
|
||||||
"ai_1": {
|
"ai_1": {
|
||||||
"alive": true,
|
"alive": true,
|
||||||
@@ -1475,12 +1468,12 @@
|
|||||||
"eliminatedRound": null,
|
"eliminatedRound": null,
|
||||||
"lp": 400,
|
"lp": 400,
|
||||||
"bandId": "band_doberm",
|
"bandId": "band_doberm",
|
||||||
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
|
"title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"},
|
||||||
"trophies": 8,
|
"trophies": 8,
|
||||||
"reward": 425,
|
"reward": 425,
|
||||||
"stats": {"activatedLayers":2623,"bossDamage":4551311,"dmgDealt":18984249,"gold":197,"itemsEquipped":35,"kills":283,"leaks":1,"lpLost":0,"merges":6,"perfectRounds":12,"refreshes":35},
|
"stats": {"activatedLayers":2322,"bossDamage":2829244,"dmgDealt":16856376,"gold":174,"itemsEquipped":28,"kills":280,"leaks":6,"lpLost":0,"merges":4,"perfectRounds":11,"refreshes":40},
|
||||||
"lineup": ["1_13_b@11,5[4_06_e_b+4_09_e_a]","6_11_a@11,8[3_08_e_a+5_01_e_a]","6_08_a@10,3[1_05_e_b+2_01_e_b]","3_16_b@10,4[3_10_e_b+2_03_e_b]","4_01_a@10,7[3_01_e_a+6_09_e_a]","4_07_b@10,8[3_10_e_a+5_09_e_a]","6_01_a@9,8[3_05_e_b+3_02_e_b]","1_06_b@9,9[3_11_e_a+2_03_e_b]"],
|
"lineup": ["2_17_a@12,8[3_08_e_a+4_02_e_a]","5_03_a@11,8[3_02_e_a+3_10_e_a]","1_10_b@10,4[4_11_e_a+2_01_e_b]","4_11_a@10,5[2_03_e_b+3_10_e_b]","5_06_a@10,8[3_01_e_b+5_02_e_a]","3_16_a@9,3[2_03_e_b+3_09_e_a]","6_20_a@9,6[1_05_e_a+4_08_e_b]","1_06_b@9,9[3_08_e_b+1_05_e_b]"],
|
||||||
"bonds": ["arcaneShip 0/302","deputShip 1/328","egirShip 0/300","emptyShip 0/300","indomShip 1/322","investShip 0/300","kazimierzShip 0/300","kjeragShip 0/300","lateranoShip 1/304","maniShip 1/304","miraShip 0/300","preciShip 1/323","raidShip 0/300","sargonShip 0/300","siracusaShip 0/300","skillfulShip 0/304","soloShip 0/322","steadShip 1/390","suntShip 1/312","swiftShip 0/300","victoriaShip 1/340","visiShip 0/306","yanShip 0/310"]
|
"bonds": ["arcaneShip 0/302","deputShip 1/328","egirShip 0/300","emptyShip 0/300","indomShip 1/326","investShip 0/300","kazimierzShip 0/300","kjeragShip 0/300","lateranoShip 0/300","maniShip 0/300","miraShip 0/300","preciShip 0/309","raidShip 0/300","sargonShip 0/300","siracusaShip 0/300","skillfulShip 1/314","soloShip 1/318","steadShip 2/384","suntShip 1/310","swiftShip 0/300","victoriaShip 1/342","visiShip 0/306","yanShip 0/310"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,14 @@ import { ERR, PHASE } from '../../shared/constants.js';
|
|||||||
import { GameData } from '../../server/match/gamedata.js';
|
import { GameData } from '../../server/match/gamedata.js';
|
||||||
import { DATA, makeMatch, give, giveItem, checkInvariants, chessOfTier } from './harness.js';
|
import { DATA, makeMatch, give, giveItem, checkInvariants, chessOfTier } from './harness.js';
|
||||||
|
|
||||||
|
/** Fill every free hand slot with distinct plain equipment (nothing merges), so a piece put into temp stays there. */
|
||||||
|
function fillHand(ps) {
|
||||||
|
const plain = Object.values(DATA.items).filter((i) => i.itemType === 'EQUIP' && !i.isGolden && i.kind === 'passive').map((i) => i.itemId ?? i.id);
|
||||||
|
let k = 0;
|
||||||
|
for (let i = 0; i < ps.hand.length; i++) if (ps.hand[i] == null) ps.hand[i] = ps.newPiece('item', plain[k++]);
|
||||||
|
ps.recompute();
|
||||||
|
}
|
||||||
|
|
||||||
test('income = min(3 + r, 12) in every mode; funds are lost at prep end (band_cannot keeps them)', () => {
|
test('income = min(3 + r, 12) in every mode; funds are lost at prep end (band_cannot keeps them)', () => {
|
||||||
for (const modeId of ['mode_single_funny', 'mode_multi_normal', 'mode_multi_abyss']) {
|
for (const modeId of ['mode_single_funny', 'mode_multi_normal', 'mode_multi_abyss']) {
|
||||||
const gd = new GameData(DATA, modeId);
|
const gd = new GameData(DATA, modeId);
|
||||||
@@ -190,16 +198,21 @@ test('selling: +1 (normal and elite, board or hand), items return to the hand, i
|
|||||||
m.dispose();
|
m.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('ready gating: temp must be empty; unready allowed until everyone is ready; actions locked while ready', () => {
|
test('ready gating: temp must be empty (a freed hand slot pulls the temp piece in); unready allowed until everyone is ready; actions locked while ready', () => {
|
||||||
const h = makeMatch({ mode: 'coop', humans: 2, seed: 10 }).start();
|
const h = makeMatch({ mode: 'coop', humans: 2, seed: 10 }).start();
|
||||||
h.toPrep(1);
|
h.toPrep(1);
|
||||||
const m = h.m;
|
const m = h.m;
|
||||||
const a = h.ps('p_0');
|
const a = h.ps('p_0');
|
||||||
|
fillHand(a);
|
||||||
const t = give(m, a, chessOfTier(1).find((x) => m.pool.has(x)), 'temp');
|
const t = give(m, a, chessOfTier(1).find((x) => m.pool.has(x)), 'temp');
|
||||||
|
assert.ok(a.temp.includes(t), 'a full hand: it waits in temp');
|
||||||
assert.equal(a.privateView().canReady, false);
|
assert.equal(a.privateView().canReady, false);
|
||||||
assert.deepEqual(m.handle('p_0', { t: 'g.ready', ready: true }), { error: ERR.TEMP_NOT_EMPTY });
|
assert.deepEqual(m.handle('p_0', { t: 'g.ready', ready: true }), { error: ERR.TEMP_NOT_EMPTY });
|
||||||
const free = a.hand.findIndex((x) => x == null);
|
// destroying a hand item frees its slot: the temp piece moves in by itself (PRTS 卫戍协议/帮助 §手牌区, GitHub #82)
|
||||||
assert.deepEqual(m.handle('p_0', { t: 'g.move', uid: t.uid, to: { area: 'hand', idx: free } }), { ok: true });
|
const freed = a.hand[3];
|
||||||
|
assert.deepEqual(m.handle('p_0', { t: 'g.destroy', uid: freed.uid }), { ok: true });
|
||||||
|
assert.equal(a.hand[3], t);
|
||||||
|
assert.ok(a.tempEmpty);
|
||||||
assert.equal(a.privateView().canReady, true);
|
assert.equal(a.privateView().canReady, true);
|
||||||
assert.deepEqual(m.handle('p_0', { t: 'g.ready', ready: true }), { ok: true });
|
assert.deepEqual(m.handle('p_0', { t: 'g.ready', ready: true }), { ok: true });
|
||||||
assert.deepEqual(m.handle('p_0', { t: 'g.refresh' }), { error: ERR.WRONG_PHASE, detail: 'ready' });
|
assert.deepEqual(m.handle('p_0', { t: 'g.refresh' }), { error: ERR.WRONG_PHASE, detail: 'ready' });
|
||||||
@@ -222,7 +235,9 @@ test('prep deadline (co-op): unready players are auto-readied and their temp is
|
|||||||
const a = h.ps('p_0');
|
const a = h.ps('p_0');
|
||||||
const id = chessOfTier(1).find((x) => m.pool.has(x));
|
const id = chessOfTier(1).find((x) => m.pool.has(x));
|
||||||
const left = m.pool.left(id);
|
const left = m.pool.left(id);
|
||||||
|
fillHand(a);
|
||||||
give(m, a, id, 'temp');
|
give(m, a, id, 'temp');
|
||||||
|
assert.ok(!a.tempEmpty, 'a full hand: it waits in temp');
|
||||||
assert.ok(m.deadline > h.sched.now(), 'co-op prep is timed');
|
assert.ok(m.deadline > h.sched.now(), 'co-op prep is timed');
|
||||||
assert.equal(Math.round((m.deadline - h.sched.now()) / 1000), m.gd.prepTime(1));
|
assert.equal(Math.round((m.deadline - h.sched.now()) / 1000), m.gd.prepTime(1));
|
||||||
h.sched.advance(m.deadline - h.sched.now() + 1);
|
h.sched.advance(m.deadline - h.sched.now() + 1);
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
// GitHub #82 (second part): the hand rules of PRTS 卫戍协议/帮助 §手牌区 — "手牌区域分为10个常规手牌区和5个临时手牌区。当
|
||||||
|
// 常规手牌区全满无空位时,玩家将无法执行使手牌溢出的操作" ("例如招募/购入等通常情况下会增加手牌的操作") and "溢出单位会自动
|
||||||
|
// 进入临时手牌区,常规手牌区出现空位时自动移入".
|
||||||
|
// * a full regular hand refuses every purchase and reward pick — also one whose copy would complete a merge at once
|
||||||
|
// (the reporter's first-hand check in the official game; BWIKI 盟约 "无法进行涉及到整备区的操作(购买、撤回等)")
|
||||||
|
// * a free regular slot pulls a temp piece in at once (PlayerState._fillHandFromTemp, run by every recompute): after a
|
||||||
|
// sale, a deployment from the hand, a merge that consumed hand copies, an item destroyed / equipped from the hand, a
|
||||||
|
// summon stack removed with its owner. The temp row empties right → left (the order it fills) [ASSUMED]; each piece
|
||||||
|
// takes the hand's next free slot right → left.
|
||||||
|
import { test } from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { ERR } from '../../shared/constants.js';
|
||||||
|
import { DATA, makeMatch, give, giveItem, checkInvariants, chessOfTier, legalTileFor } from './harness.js';
|
||||||
|
|
||||||
|
/** Plain equipment (no equip / merge side effects), all different: hand fillers that never merge with each other. */
|
||||||
|
const PLAIN = Object.values(DATA.items)
|
||||||
|
.filter((i) => i.itemType === 'EQUIP' && !i.isGolden && i.kind === 'passive')
|
||||||
|
.map((i) => i.itemId ?? i.id).filter(Boolean);
|
||||||
|
|
||||||
|
/** Solo 标准 (untimed) PREP R1 with an empty board and hand, 100 funds, a known stage. */
|
||||||
|
function prep(seed) {
|
||||||
|
const h = makeMatch({ mode: 'solo', difficulty: 'NORMAL', seed }).start();
|
||||||
|
h.toPrep(1);
|
||||||
|
h.setStage('act2autochess_m04');
|
||||||
|
const ps = h.ps('p_0');
|
||||||
|
for (const p of [...ps.board.values(), ...ps.hand.filter(Boolean)]) if (p.kind === 'chess') ps.returnCopies(p);
|
||||||
|
ps.board.clear();
|
||||||
|
ps.hand.fill(null);
|
||||||
|
ps.recompute();
|
||||||
|
ps.funds = 100;
|
||||||
|
return { h, m: h.m, ps };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fill every free hand slot with plain equipment (skipping `skip` ids). */
|
||||||
|
function fillHand(ps, skip = []) {
|
||||||
|
const ids = PLAIN.filter((id) => !skip.includes(id));
|
||||||
|
let k = 0;
|
||||||
|
while (ps.hand.some((x) => x == null)) giveItem(ps.m, ps, ids[k++]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergeable = (m) => chessOfTier(1, (c) => c.position === 'MELEE').find((id) => m.pool.has(id) && m.gd.mergeCount(id) === 3);
|
||||||
|
|
||||||
|
test('a full hand refuses a purchase / pick that would complete a merge (chess, item, reward); nothing is spent', () => {
|
||||||
|
const { m, ps } = prep(8201);
|
||||||
|
const id = mergeable(m);
|
||||||
|
give(m, ps, id, 'hand', 0);
|
||||||
|
give(m, ps, id, 'hand', 1);
|
||||||
|
const twin = PLAIN[0];
|
||||||
|
giveItem(m, ps, twin, 'hand', 2);
|
||||||
|
fillHand(ps, [twin]);
|
||||||
|
assert.ok(ps.completesChessMerge(id) && ps.completesItemMerge(twin));
|
||||||
|
ps.shop.slots[0] = { kind: 'chess', id, basePrice: m.gd.chessPrice(id), frozen: false, sold: false };
|
||||||
|
const itemSlot = ps.shop.slots.findIndex((s, i) => i > 0 && s && s.kind === 'item');
|
||||||
|
ps.shop.slots[itemSlot] = { kind: 'item', id: twin, basePrice: 2, frozen: false, sold: false };
|
||||||
|
const left = m.pool.left(id);
|
||||||
|
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 0 }), { error: ERR.HAND_FULL }, 'the third copy of a held pair');
|
||||||
|
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: itemSlot }), { error: ERR.HAND_FULL }, 'an item whose twin is in the hand');
|
||||||
|
ps.pushRewardOffer('merge', { ids: [id] });
|
||||||
|
assert.deepEqual(m.handle('p_0', { t: 'g.reward', idx: 0 }), { error: ERR.HAND_FULL }, 'a merge-completing reward pick');
|
||||||
|
assert.equal(ps.funds, 100, 'nothing spent');
|
||||||
|
assert.equal(m.pool.left(id), left, 'no copy taken');
|
||||||
|
assert.ok(!ps.shop.slots[0].sold && !ps.shop.slots[itemSlot].sold);
|
||||||
|
assert.equal(ps.countCopies(m.gd.baseIdOf(id)), 2, 'no merge');
|
||||||
|
// a free slot (an item destroyed) lets the purchase through: it completes the merge, the elite goes to the hand
|
||||||
|
assert.deepEqual(m.handle('p_0', { t: 'g.destroy', uid: ps.hand[9].uid }), { ok: true });
|
||||||
|
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 0 }), { ok: true });
|
||||||
|
assert.ok(ps.hand.some((p) => p && p.id === m.gd.goldenIdOf(id)), 'the elite in the hand');
|
||||||
|
assert.equal(ps.hand.filter(Boolean).length, 8, '10 − the destroyed item − 2 copies + the elite');
|
||||||
|
checkInvariants(m);
|
||||||
|
m.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('selling / destroying from a full hand pulls the temp pieces in, rightmost first, into the freed slot; Ready follows', () => {
|
||||||
|
const { m, ps } = prep(8202);
|
||||||
|
const sold = give(m, ps, chessOfTier(2).find((x) => m.pool.has(x)), 'hand', 4);
|
||||||
|
fillHand(ps);
|
||||||
|
const ids = chessOfTier(3).filter((x) => m.pool.has(x));
|
||||||
|
const first = give(m, ps, ids[0], 'temp', 4); // the first overflow takes the rightmost temp slot (stow → freeSlot)
|
||||||
|
const second = give(m, ps, ids[1], 'temp', 3);
|
||||||
|
assert.ok(ps.temp.includes(first) && ps.temp.includes(second), 'a full hand: both wait in temp');
|
||||||
|
assert.equal(ps.privateView().canReady, false);
|
||||||
|
checkInvariants(m);
|
||||||
|
assert.deepEqual(m.handle('p_0', { t: 'g.sell', uid: sold.uid }), { ok: true });
|
||||||
|
assert.equal(ps.hand[4], first, 'the rightmost temp piece moves into the freed slot');
|
||||||
|
assert.ok(ps.temp.includes(second) && !ps.temp.includes(first));
|
||||||
|
assert.equal(ps._tempDue.has(first.uid), false, 'no longer due at the prep deadline');
|
||||||
|
assert.equal(ps.privateView().canReady, false, 'one left in temp');
|
||||||
|
checkInvariants(m);
|
||||||
|
const item = ps.hand[7];
|
||||||
|
assert.equal(item.kind, 'item');
|
||||||
|
assert.deepEqual(m.handle('p_0', { t: 'g.destroy', uid: item.uid }), { ok: true });
|
||||||
|
assert.equal(ps.hand[7], second);
|
||||||
|
assert.ok(ps.tempEmpty);
|
||||||
|
assert.equal(ps.privateView().canReady, true);
|
||||||
|
assert.deepEqual(m.handle('p_0', { t: 'g.ready', ready: true }), { ok: true });
|
||||||
|
checkInvariants(m);
|
||||||
|
m.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('deploying a card from a full hand, or equipping an item from it, pulls a temp piece into the freed slot', () => {
|
||||||
|
const { m, ps } = prep(8203);
|
||||||
|
const id = chessOfTier(1, (c) => c.position === 'MELEE' && !m.gd.placeableTokens(c.chessId).length).find((x) => m.pool.has(x));
|
||||||
|
const card = give(m, ps, id, 'hand', 9);
|
||||||
|
fillHand(ps);
|
||||||
|
const t1 = give(m, ps, chessOfTier(2).find((x) => m.pool.has(x)), 'temp', 4);
|
||||||
|
const t2 = giveItem(m, ps, PLAIN[PLAIN.length - 1], 'temp', 3);
|
||||||
|
const [r, c] = legalTileFor(m, ps, id);
|
||||||
|
assert.deepEqual(m.handle('p_0', { t: 'g.move', uid: card.uid, to: { area: 'board', row: r, col: c } }), { ok: true });
|
||||||
|
assert.equal(ps.hand[9], t1, 'the deployed card\'s slot took the temp piece');
|
||||||
|
checkInvariants(m);
|
||||||
|
const it = ps.hand[0];
|
||||||
|
assert.deepEqual(m.handle('p_0', { t: 'g.equip', itemUid: it.uid, targetUid: card.uid }), { ok: true });
|
||||||
|
assert.equal(ps.hand[0], t2, 'the equipped item\'s slot took the temp piece');
|
||||||
|
assert.ok(ps.tempEmpty);
|
||||||
|
checkInvariants(m);
|
||||||
|
m.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a gain that merges two hand copies: the elite takes one freed slot, the other pulls a temp piece in', () => {
|
||||||
|
const { m, ps } = prep(8204);
|
||||||
|
const id = mergeable(m);
|
||||||
|
give(m, ps, id, 'hand', 0);
|
||||||
|
give(m, ps, id, 'hand', 1);
|
||||||
|
fillHand(ps);
|
||||||
|
const ids = chessOfTier(3).filter((x) => m.pool.has(x));
|
||||||
|
const t1 = give(m, ps, ids[0], 'temp', 4);
|
||||||
|
const t2 = give(m, ps, ids[1], 'temp', 3);
|
||||||
|
const elite = ps.acquireChess(id, { source: 'grant' });
|
||||||
|
assert.ok(elite && m.gd.isGolden(elite.id));
|
||||||
|
assert.equal(ps.hand[1], elite, 'the elite: the hand\'s next free slot (right → left)');
|
||||||
|
assert.equal(ps.hand[0], t1, 'the other freed slot: the rightmost temp piece');
|
||||||
|
assert.deepEqual(ps.temp.filter(Boolean), [t2], 'the hand is full again: the other waits');
|
||||||
|
checkInvariants(m);
|
||||||
|
m.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('selling a deployed summoner removes its summon stack from the hand: that slot pulls a temp piece in', () => {
|
||||||
|
const { m, ps } = prep(8205);
|
||||||
|
const owner = Object.values(DATA.chess).find((c) => c.visible && !c.isGolden && m.pool.has(c.chessId) && m.gd.placeableTokens(c.chessId).length && legalTileFor(m, ps, c.chessId));
|
||||||
|
assert.ok(owner, 'a chess with a manually deployable summon');
|
||||||
|
const op = give(m, ps, owner.chessId, 'board', legalTileFor(m, ps, owner.chessId));
|
||||||
|
const stackAt = ps.hand.findIndex((p) => p && p.kind === 'token' && p.ownerUid === op.uid);
|
||||||
|
assert.ok(stackAt >= 0, 'its summon stack joined the hand');
|
||||||
|
fillHand(ps);
|
||||||
|
const t = give(m, ps, chessOfTier(2).find((x) => m.pool.has(x)), 'temp', 4);
|
||||||
|
assert.deepEqual(m.handle('p_0', { t: 'g.sell', uid: op.uid }), { ok: true });
|
||||||
|
assert.equal(ps.hand[stackAt], t, 'the stack\'s slot took the temp piece');
|
||||||
|
assert.ok(ps.tempEmpty);
|
||||||
|
checkInvariants(m);
|
||||||
|
m.dispose();
|
||||||
|
});
|
||||||
@@ -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();
|
||||||
|
});
|
||||||
@@ -85,7 +85,10 @@ test('merge with a full hand and a full temp: the elite takes its deployed copy\
|
|||||||
for (let i = 0; i < 5; i++) give(m, ps, fillers[10 + i], 'temp', i);
|
for (let i = 0; i < 5; i++) give(m, ps, fillers[10 + i], 'temp', i);
|
||||||
ps.shop.slots[0] = { kind: 'chess', id, basePrice: m.gd.chessPrice(id), frozen: false, sold: false };
|
ps.shop.slots[0] = { kind: 'chess', id, basePrice: m.gd.chessPrice(id), frozen: false, sold: false };
|
||||||
checkInvariants(m);
|
checkInvariants(m);
|
||||||
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 0 }), { ok: true }, 'a purchase that completes a merge is allowed with a full hand');
|
// a full hand refuses the purchase, also one that would complete the merge (PRTS 卫戍协议/帮助 §手牌区, GitHub #82);
|
||||||
|
// a gain (an effect's grant) still completes it
|
||||||
|
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 0 }), { error: 'HAND_FULL' }, 'a full hand refuses the merge-completing purchase');
|
||||||
|
assert.ok(ps.acquireChess(id, { source: 'grant' }), 'the gained copy completes the merge');
|
||||||
const elite = [...ps.board.values()].find((p) => p.id === m.gd.goldenIdOf(id));
|
const elite = [...ps.board.values()].find((p) => p.id === m.gd.goldenIdOf(id));
|
||||||
assert.ok(elite, 'the elite stands on a consumed copy\'s tile');
|
assert.ok(elite, 'the elite stands on a consumed copy\'s tile');
|
||||||
assert.equal(ps.find(elite.uid).key, `${t1[0]},${t1[1]}`, 'the copy that deploys first (legalTileFor walks the top row left to right: on one row, the deploy order)');
|
assert.equal(ps.find(elite.uid).key, `${t1[0]},${t1[1]}`, 'the copy that deploys first (legalTileFor walks the top row left to right: on one row, the deploy order)');
|
||||||
@@ -260,7 +263,7 @@ test('garrisonHooks widens a garrison to extra hooks ("<进入休整期时><休
|
|||||||
m.dispose();
|
m.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('ctx.offerItems queues an item offer; g.reward takes the item (full hand refused unless it merges)', () => {
|
test('ctx.offerItems queues an item offer; g.reward takes the item (a full hand refuses it, also when it would merge)', () => {
|
||||||
const reg = builtins();
|
const reg = builtins();
|
||||||
const { m, ps } = prepSolo({ seed: 913, registry: reg });
|
const { m, ps } = prepSolo({ seed: 913, registry: reg });
|
||||||
const ctx = makeCtx(m, ps, { kind: 'band', key: 'band:test' }, 'onLevelUp');
|
const ctx = makeCtx(m, ps, { kind: 'band', key: 'band:test' }, 'onLevelUp');
|
||||||
@@ -269,8 +272,10 @@ test('ctx.offerItems queues an item offer; g.reward takes the item (full hand re
|
|||||||
const view = ps.privateView().shop.rewardOffer;
|
const view = ps.privateView().shop.rewardOffer;
|
||||||
assert.deepEqual(view.slots.map((s) => [s.kind, s.id, s.price]), [['item', 'chess_item_1_01_e_a', 0], ['item', 'chess_item_2_03_e_a', 0]]);
|
assert.deepEqual(view.slots.map((s) => [s.kind, s.id, s.price]), [['item', 'chess_item_1_01_e_a', 0], ['item', 'chess_item_2_03_e_a', 0]]);
|
||||||
const fillers = chessOfTier(1).filter((c) => m.pool.has(c));
|
const fillers = chessOfTier(1).filter((c) => m.pool.has(c));
|
||||||
for (let i = 0; i < 10; i++) give(m, ps, fillers[i], 'hand', i);
|
for (let i = 0; i < 9; i++) give(m, ps, fillers[i], 'hand', i);
|
||||||
assert.equal(m.handle('p_0', { t: 'g.reward', idx: 0 }).error, 'HAND_FULL');
|
giveItem(m, ps, 'chess_item_1_01_e_a', 'hand', 9);
|
||||||
|
assert.equal(ps.completesItemMerge('chess_item_1_01_e_a'), true);
|
||||||
|
assert.equal(m.handle('p_0', { t: 'g.reward', idx: 0 }).error, 'HAND_FULL', 'refused although it would merge (PRTS 卫戍协议/帮助 §手牌区)');
|
||||||
ps.hand[0] && ps.returnCopies(ps.hand[0]);
|
ps.hand[0] && ps.returnCopies(ps.hand[0]);
|
||||||
ps.hand[0] = null;
|
ps.hand[0] = null;
|
||||||
ps.recompute();
|
ps.recompute();
|
||||||
|
|||||||
+22
-10
@@ -67,7 +67,7 @@ test('3 copies (board + hand + bought) merge into 1 elite on the board copy\'s t
|
|||||||
m.dispose();
|
m.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('elites never merge; 风丸 merges with 2 copies; a full hand still buys the merge-completing copy', () => {
|
test('elites never merge; 风丸 merges with 2 copies; a full hand refuses the merge-completing copy too (GitHub #82)', () => {
|
||||||
const { m, ps } = prep(22);
|
const { m, ps } = prep(22);
|
||||||
const id = chessOfTier(2).find((x) => m.pool.has(x) && x !== 'chess_char_2_11_a');
|
const id = chessOfTier(2).find((x) => m.pool.has(x) && x !== 'chess_char_2_11_a');
|
||||||
const golden = DATA.chess[id].goldenId;
|
const golden = DATA.chess[id].goldenId;
|
||||||
@@ -83,7 +83,9 @@ test('elites never merge; 风丸 merges with 2 copies; a full hand still buys th
|
|||||||
m.handle('p_0', { t: 'g.buy', slot: 0 });
|
m.handle('p_0', { t: 'g.buy', slot: 0 });
|
||||||
assert.ok(ps.hand.some((p) => p && p.id === DATA.chess.chess_char_2_11_a.goldenId), '风丸 elite after 2 copies');
|
assert.ok(ps.hand.some((p) => p && p.id === DATA.chess.chess_char_2_11_a.goldenId), '风丸 elite after 2 copies');
|
||||||
}
|
}
|
||||||
// full hand + 2 copies in the hand: buying the 3rd completes the merge (net −1)
|
// full hand + 2 copies in the hand: the 3rd copy is refused like any purchase (PRTS 卫戍协议/帮助 §手牌区 "当常规手牌区
|
||||||
|
// 全满无空位时,玩家将无法执行使手牌溢出的操作" — "例如招募/购入"; GitHub #82); a sale frees a slot, then the purchase
|
||||||
|
// completes the merge (the elite in the hand)
|
||||||
for (const p of ps.hand) if (p) ps.returnCopies(p);
|
for (const p of ps.hand) if (p) ps.returnCopies(p);
|
||||||
ps.hand.fill(null);
|
ps.hand.fill(null);
|
||||||
const x = chessOfTier(1).filter((c) => m.pool.has(c));
|
const x = chessOfTier(1).filter((c) => m.pool.has(c));
|
||||||
@@ -95,15 +97,19 @@ test('elites never merge; 风丸 merges with 2 copies; a full hand still buys th
|
|||||||
stock(m, ps, x[20] || x[x.length - 1], 1);
|
stock(m, ps, x[20] || x[x.length - 1], 1);
|
||||||
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 1 }), { error: ERR.HAND_FULL });
|
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 1 }), { error: ERR.HAND_FULL });
|
||||||
stock(m, ps, target, 0);
|
stock(m, ps, target, 0);
|
||||||
|
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 0 }), { error: ERR.HAND_FULL }, 'the merge-completing copy too');
|
||||||
|
assert.ok(!ps.hand.some((p) => p && p.id === DATA.chess[target].goldenId));
|
||||||
|
const filler = ps.hand.find((p) => p && p.id !== target);
|
||||||
|
assert.deepEqual(m.handle('p_0', { t: 'g.sell', uid: filler.uid }), { ok: true });
|
||||||
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 0 }), { ok: true });
|
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 0 }), { ok: true });
|
||||||
assert.ok(ps.hand.some((p) => p && p.id === DATA.chess[target].goldenId));
|
assert.ok(ps.hand.some((p) => p && p.id === DATA.chess[target].goldenId));
|
||||||
assert.equal(ps.hand.filter(Boolean).length, 9);
|
assert.equal(ps.hand.filter(Boolean).length, 8, '10 − the sold filler − 2 copies + the elite');
|
||||||
assert.ok(ps.tempEmpty);
|
assert.ok(ps.tempEmpty);
|
||||||
checkInvariants(m);
|
checkInvariants(m);
|
||||||
m.dispose();
|
m.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('merge from two board copies with a full hand: the elite takes the copy that deploys first (no overflow, one tile freed)', () => {
|
test('merge from two board copies with a full hand (a gained copy; a purchase is refused): the elite takes the copy that deploys first (no overflow, one tile freed)', () => {
|
||||||
const { m, ps } = prep(23);
|
const { m, ps } = prep(23);
|
||||||
const ids = chessOfTier(1, (c) => c.position === 'MELEE').filter((c) => m.pool.has(c));
|
const ids = chessOfTier(1, (c) => c.position === 'MELEE').filter((c) => m.pool.has(c));
|
||||||
const id = ids[0];
|
const id = ids[0];
|
||||||
@@ -115,7 +121,8 @@ test('merge from two board copies with a full hand: the elite takes the copy tha
|
|||||||
for (let i = 0; ps.hand.some((p) => p == null); i++) give(m, ps, others[i]);
|
for (let i = 0; ps.hand.some((p) => p == null); i++) give(m, ps, others[i]);
|
||||||
stock(m, ps, id);
|
stock(m, ps, id);
|
||||||
assert.equal(ps.deployCount, 2);
|
assert.equal(ps.deployCount, 2);
|
||||||
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 0 }), { ok: true });
|
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 0 }), { error: ERR.HAND_FULL }, 'a full hand refuses the purchase (GitHub #82)');
|
||||||
|
assert.ok(ps.acquireChess(id, { source: 'grant' }), 'a gain (an effect\'s grant) completes the merge');
|
||||||
// deploy order = by column from the left (col asc, then row desc; Battle.start): (9,3) before (9,4) — [ASSUMED] the copy
|
// deploy order = by column from the left (col asc, then row desc; Battle.start): (9,3) before (9,4) — [ASSUMED] the copy
|
||||||
// the battle deploys first
|
// the battle deploys first
|
||||||
const elite = ps.board.get('9,3');
|
const elite = ps.board.get('9,3');
|
||||||
@@ -134,12 +141,14 @@ test('merge whose copies are not deployed (temp) with a full hand overflows the
|
|||||||
const { m, ps } = prep(23);
|
const { m, ps } = prep(23);
|
||||||
const ids = chessOfTier(1, (c) => c.position === 'MELEE').filter((c) => m.pool.has(c));
|
const ids = chessOfTier(1, (c) => c.position === 'MELEE').filter((c) => m.pool.has(c));
|
||||||
const id = ids[0];
|
const id = ids[0];
|
||||||
give(m, ps, id, 'temp', 0);
|
|
||||||
give(m, ps, id, 'temp', 1);
|
|
||||||
const others = chessOfTier(2).filter((c) => m.pool.has(c));
|
const others = chessOfTier(2).filter((c) => m.pool.has(c));
|
||||||
for (let i = 0; ps.hand.some((p) => p == null); i++) give(m, ps, others[i]);
|
for (let i = 0; ps.hand.some((p) => p == null); i++) give(m, ps, others[i]);
|
||||||
stock(m, ps, id);
|
// with the hand full they wait in temp (a free hand slot would pull them in, PRTS 卫戍协议/帮助 §手牌区)
|
||||||
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 0 }), { ok: true });
|
give(m, ps, id, 'temp', 0);
|
||||||
|
give(m, ps, id, 'temp', 1);
|
||||||
|
assert.equal(ps.temp.filter(Boolean).length, 2);
|
||||||
|
// a gain completes the merge (a purchase is refused with a full hand)
|
||||||
|
assert.ok(ps.acquireChess(id, { source: 'grant' }));
|
||||||
assert.ok(ps.temp.some((p) => p && p.id === DATA.chess[id].goldenId), 'elite overflowed into temp');
|
assert.ok(ps.temp.some((p) => p && p.id === DATA.chess[id].goldenId), 'elite overflowed into temp');
|
||||||
assert.equal(ps.board.size, 0, 'nothing was deployed');
|
assert.equal(ps.board.size, 0, 'nothing was deployed');
|
||||||
assert.deepEqual(m.handle('p_0', { t: 'g.ready', ready: true }), { error: ERR.TEMP_NOT_EMPTY });
|
assert.deepEqual(m.handle('p_0', { t: 'g.ready', ready: true }), { error: ERR.TEMP_NOT_EMPTY });
|
||||||
@@ -187,11 +196,14 @@ test('equipment: max 2 (a third replaces the oldest), identical normal items mer
|
|||||||
assert.ok(ps.hand.some((p) => p && p.id === 'chess_item_1_02_e_b'), 'golden item in the hand');
|
assert.ok(ps.hand.some((p) => p && p.id === 'chess_item_1_02_e_b'), 'golden item in the hand');
|
||||||
assert.ok(!a.items.some((x) => x.id === 'chess_item_1_02_e_a'));
|
assert.ok(!a.items.some((x) => x.id === 'chess_item_1_02_e_a'));
|
||||||
assert.equal(ps.stats.itemMerges, 1);
|
assert.equal(ps.stats.itemMerges, 1);
|
||||||
// buying an item whose twin is in the hand merges immediately (and works with a full hand)
|
// buying an item whose twin is in the hand merges immediately — but a full hand refuses the purchase all the same
|
||||||
|
// (PRTS 卫戍协议/帮助 §手牌区, GitHub #82)
|
||||||
const i5 = giveItem(m, ps, 'chess_item_2_03_e_a');
|
const i5 = giveItem(m, ps, 'chess_item_2_03_e_a');
|
||||||
const others = chessOfTier(1).filter((c) => m.pool.has(c));
|
const others = chessOfTier(1).filter((c) => m.pool.has(c));
|
||||||
for (let i = 0; ps.hand.some((p) => p == null); i++) give(m, ps, others[i]);
|
for (let i = 0; ps.hand.some((p) => p == null); i++) give(m, ps, others[i]);
|
||||||
ps.shop.slots[0] = { kind: 'item', id: 'chess_item_2_03_e_a', basePrice: 2, frozen: false, sold: false };
|
ps.shop.slots[0] = { kind: 'item', id: 'chess_item_2_03_e_a', basePrice: 2, frozen: false, sold: false };
|
||||||
|
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 0 }), { error: ERR.HAND_FULL });
|
||||||
|
assert.deepEqual(m.handle('p_0', { t: 'g.sell', uid: ps.hand.find((p) => p && p.kind === 'chess' && p !== a).uid }), { ok: true });
|
||||||
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 0 }), { ok: true });
|
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 0 }), { ok: true });
|
||||||
assert.ok(ps.hand.some((p) => p && p.id === 'chess_item_2_03_e_b'));
|
assert.ok(ps.hand.some((p) => p && p.id === 'chess_item_2_03_e_b'));
|
||||||
assert.ok(!ps.find(i5.uid));
|
assert.ok(!ps.find(i5.uid));
|
||||||
|
|||||||
@@ -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)
|
// 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' };
|
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)) {
|
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 lv = JSON.parse(readFileSync(levelFile(id), 'utf8'));
|
||||||
const map = lv.mapData.map, tiles = lv.mapData.tiles, H = map.length;
|
const map = lv.mapData.map, tiles = lv.mapData.tiles, H = map.length;
|
||||||
const dev = new Map();
|
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', () => {
|
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)) {
|
for (const [id, st] of Object.entries(DATA.stages)) {
|
||||||
|
if (st.kind === 'unite') continue; // the 联防 maps have no boss field
|
||||||
const n = buildDeployMap(st);
|
const n = buildDeployMap(st);
|
||||||
for (const field of ['bossL', 'bossR']) {
|
for (const field of ['bossL', 'bossR']) {
|
||||||
const b = buildDeployMap(st, { field });
|
const b = buildDeployMap(st, { field });
|
||||||
|
|||||||
@@ -199,7 +199,8 @@ test('equipment with the hand and temp full: the elite keeps up to its 2 equip s
|
|||||||
const warn = m.log.warn;
|
const warn = m.log.warn;
|
||||||
m.log.warn = (s) => { warns.push(String(s)); };
|
m.log.warn = (s) => { warns.push(String(s)); };
|
||||||
stock(m, ps, id);
|
stock(m, ps, id);
|
||||||
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 0 }), { ok: true }, 'a merge-completing buy needs no hand slot');
|
assert.equal(m.handle('p_0', { t: 'g.buy', slot: 0 }).error, 'HAND_FULL', 'a full hand refuses the purchase, also a merge-completing one (GitHub #82)');
|
||||||
|
assert.ok(ps.acquireChess(id, { source: 'grant' }), 'a gained copy completes the merge');
|
||||||
m.log.warn = warn;
|
m.log.warn = warn;
|
||||||
const elite = ps.board.get(want);
|
const elite = ps.board.get(want);
|
||||||
assert.ok(elite && m.gd.isGolden(elite.id), 'the elite took the first deployed copy\'s tile');
|
assert.ok(elite && m.gd.isGolden(elite.id), 'the elite took the first deployed copy\'s tile');
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
import { test } from 'node:test';
|
import { test } from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import { GEO, PHASE } from '../../shared/constants.js';
|
import { GEO, PHASE } from '../../shared/constants.js';
|
||||||
import { makeMatch, give, giveItem, legalTileFor, chessOfTier } from './harness.js';
|
import { DATA, makeMatch, give, giveItem, legalTileFor, chessOfTier } from './harness.js';
|
||||||
|
|
||||||
const MELEE = (c) => c.position === 'MELEE' && c.profession === 'TANK';
|
const MELEE = (c) => c.position === 'MELEE' && c.profession === 'TANK';
|
||||||
|
|
||||||
@@ -98,7 +98,12 @@ test("PR #129 review: moving a hand piece to another slot is a scout change (x f
|
|||||||
b.recompute();
|
b.recompute();
|
||||||
const id = chessOfTier(1, MELEE).find((x) => m.pool.has(x));
|
const id = chessOfTier(1, MELEE).find((x) => m.pool.has(x));
|
||||||
const a = give(m, b, id, 'hand', 0);
|
const a = give(m, b, id, 'hand', 0);
|
||||||
|
// a full hand (distinct plain equipment: nothing merges), so the temp piece stays in temp — a free hand slot would pull
|
||||||
|
// it in (PRTS 卫戍协议/帮助 §手牌区 "常规手牌区出现空位时自动移入")
|
||||||
|
const plain = Object.values(DATA.items).filter((i) => i.itemType === 'EQUIP' && !i.isGolden && i.kind === 'passive').map((i) => i.itemId ?? i.id);
|
||||||
|
for (let i = 1; i < b.hand.length; i++) b.hand[i] = b.newPiece('item', plain[i]);
|
||||||
const c = give(m, b, id, 'temp', 0); // 临时整备区: PRTS 帮助把它算进手牌区,一起侦察
|
const c = give(m, b, id, 'temp', 0); // 临时整备区: PRTS 帮助把它算进手牌区,一起侦察
|
||||||
|
assert.ok(b.temp.includes(c));
|
||||||
assert.deepEqual(m.handle('p_0', { t: 'g.watch', fieldId: 'n:p_1' }), { ok: true });
|
assert.deepEqual(m.handle('p_0', { t: 'g.watch', fieldId: 'n:p_1' }), { ok: true });
|
||||||
|
|
||||||
const first = h.lastTo('p_0', 'm.field');
|
const first = h.lastTo('p_0', 'm.field');
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import { bossFieldPlacement } from '../../server/match/finalAssault.js';
|
|||||||
|
|
||||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
||||||
const STAGES = JSON.parse(readFileSync(path.join(ROOT, 'data/stages.json'), 'utf8'));
|
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 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 });
|
const E = (enemyKey, count, gate, t, extra = {}) => ({ enemyKey, count, gate, t, fly: false, elite: false, boss: false, source: 'wave', ...extra });
|
||||||
|
|||||||
@@ -26,9 +26,11 @@ describe('parseStage', () => {
|
|||||||
assert.ok(t.alpha > 0 && t.alpha <= 1);
|
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 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++) {
|
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].glyph, 'a', `${id} hand ${c}`);
|
||||||
assert.equal(g[7][c].h, TILE_H.bench);
|
assert.equal(g[7][c].h, TILE_H.bench);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,16 +247,19 @@ describe('full hand: shop and promotion cards', () => {
|
|||||||
const privFull = { alive: true, ready: false, funds: 20, board: [], temp: new Array(GEO.TEMP_SIZE).fill(null), shop: {},
|
const privFull = { alive: true, ready: false, funds: 20, board: [], temp: new Array(GEO.TEMP_SIZE).fill(null), shop: {},
|
||||||
hand: fullHand([{ uid: 1, kind: 'chess', id: MELEE, items: [] }, { uid: 2, kind: 'chess', id: MELEE, items: [] }]) };
|
hand: fullHand([{ uid: 1, kind: 'chess', id: MELEE, items: [] }, { uid: 2, kind: 'chess', id: MELEE, items: [] }]) };
|
||||||
const o = { priv: privFull, editable: true, getChess, getItem };
|
const o = { priv: privFull, editable: true, getChess, getItem };
|
||||||
test('a card that needs a slot is blocked with 整备区已满; one completing a merge is not', () => {
|
test('every card is blocked with 整备区已满, one completing a merge too (PRTS 卫戍协议/帮助 §手牌区, GitHub #82)', () => {
|
||||||
assert.equal(handFull(privFull), true);
|
assert.equal(handFull(privFull), true);
|
||||||
assert.equal(shopBlockReason('buy', { ...o, slot: { kind: 'chess', id: other, price: 1 } }), '整备区已满');
|
assert.equal(shopBlockReason('buy', { ...o, slot: { kind: 'chess', id: other, price: 1 } }), '整备区已满');
|
||||||
assert.equal(completesMerge(privFull, { kind: 'chess', id: MELEE }, o), true);
|
assert.equal(completesMerge(privFull, { kind: 'chess', id: MELEE }, o), true);
|
||||||
assert.equal(shopBlockReason('buy', { ...o, slot: { kind: 'chess', id: MELEE, price: 1 } }), null);
|
assert.equal(shopBlockReason('buy', { ...o, slot: { kind: 'chess', id: MELEE, price: 1 } }), '整备区已满', 'the third copy of a held pair');
|
||||||
assert.equal(shopBlockReason('reward', { ...o, slot: { kind: 'chess', id: other, price: 0 } }), '整备区已满');
|
assert.equal(shopBlockReason('reward', { ...o, slot: { kind: 'chess', id: other, price: 0 } }), '整备区已满');
|
||||||
// the hand is filled with a mergeable equipment: buying another copy merges instead of needing a slot
|
assert.equal(shopBlockReason('reward', { ...o, slot: { kind: 'chess', id: MELEE, price: 0 } }), '整备区已满', 'a merge-completing pick');
|
||||||
assert.equal(shopBlockReason('buy', { ...o, slot: { kind: 'item', id: 'chess_item_1_01_e_a', price: 1 } }), null);
|
// the hand is filled with a mergeable equipment: another copy would merge, but the purchase is refused all the same
|
||||||
|
assert.equal(completesMerge(privFull, { kind: 'item', id: 'chess_item_1_01_e_a' }, o), true);
|
||||||
|
assert.equal(shopBlockReason('buy', { ...o, slot: { kind: 'item', id: 'chess_item_1_01_e_a', price: 1 } }), '整备区已满');
|
||||||
const notFull = { ...privFull, hand: privFull.hand.map((p, i) => (i === 5 ? null : p)) };
|
const notFull = { ...privFull, hand: privFull.hand.map((p, i) => (i === 5 ? null : p)) };
|
||||||
assert.equal(shopBlockReason('buy', { ...o, priv: notFull, slot: { kind: 'chess', id: other, price: 1 } }), null);
|
assert.equal(shopBlockReason('buy', { ...o, priv: notFull, slot: { kind: 'chess', id: other, price: 1 } }), null);
|
||||||
|
assert.equal(shopBlockReason('buy', { ...o, priv: notFull, slot: { kind: 'chess', id: MELEE, price: 1 } }), null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+21
-5
@@ -2498,15 +2498,26 @@ function stageDisplayName(stageId, raw) {
|
|||||||
return name;
|
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
|
* 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) {
|
function buildStages(ctx, modesById) {
|
||||||
const researchStages = ctx.research.maps?.stages || {};
|
const researchStages = ctx.research.maps?.stages || {};
|
||||||
const out = {};
|
const out = {};
|
||||||
for (const stageId of ctx.stageIds) {
|
const unite = [['escapedBattleTemplateMapSinglePlayer', 1], ['escapedBattleTemplateMapMultiPlayer', 2]]
|
||||||
const sd = ctx.act.stageDatasDict[stageId];
|
.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 lv = ctx.levels[stageId];
|
||||||
const map = lv.mapData?.map || [];
|
const map = lv.mapData?.map || [];
|
||||||
const tiles = lv.mapData?.tiles || [];
|
const tiles = lv.mapData?.tiles || [];
|
||||||
@@ -2603,10 +2614,11 @@ function buildStages(ctx, modesById) {
|
|||||||
const p2 = officialPath(gridStage, activeBlocking, a, b);
|
const p2 = officialPath(gridStage, activeBlocking, a, b);
|
||||||
if (p2) groundPathsWithDevices[k] = p2;
|
if (p2) groundPathsWithDevices[k] = p2;
|
||||||
}
|
}
|
||||||
const modes = [...(sd.mode || [])];
|
const modes = [...(sd?.mode || [])];
|
||||||
const o = lv.options || {};
|
const o = lv.options || {};
|
||||||
out[stageId] = {
|
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,
|
modes, size: [H, W], rows, tiles: glyphTiles,
|
||||||
devices, mapChars, special, runes, globalBuffs,
|
devices, mapChars, special, runes, globalBuffs,
|
||||||
deployTiles: { normal: deployIn(9, 12, 2, 10), bossLeft: deployIn(1, 5, 2, 10), bossRight: deployIn(1, 5, 10, 18) },
|
deployTiles: { normal: deployIn(9, 12, 2, 10), bossLeft: deployIn(1, 5, 2, 10), bossRight: deployIn(1, 5, 10, 18) },
|
||||||
@@ -3515,6 +3527,10 @@ function validateAll(f) {
|
|||||||
if (s.rows.some((r) => r.includes('?'))) err(`stage ${s.id}: unknown tile glyph`);
|
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}"`);
|
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 m of Object.values(config.modes)) {
|
||||||
for (const [r, rd] of Object.entries(m.rounds)) {
|
for (const [r, rd] of Object.entries(m.rounds)) {
|
||||||
const tpls = rd.template ? [rd.template] : Object.values(rd.bossTemplates || {});
|
const tpls = rd.template ? [rd.template] : Object.values(rd.bossTemplates || {});
|
||||||
|
|||||||
+10
-5
@@ -22,8 +22,9 @@
|
|||||||
// turn; bonds from the board (bondsMeta.computeBonds) with 0–120 layers
|
// 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
|
// 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,
|
// 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
|
// ended at 200 game s) and 联防 fields (1 and 2 helpers on the 联防 map of their count — boards laid out on a
|
||||||
// leakers' enemies with a summoned-only kind and a bounty)
|
// 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,
|
// 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
|
// 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
|
// 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 { createRng, deriveSeed } from '../server/sim/rng.js';
|
||||||
import { GameData } from '../server/match/gamedata.js';
|
import { GameData } from '../server/match/gamedata.js';
|
||||||
import { setupMatchWaves, buildNormalWave, buildBossWave, buildUniteWave, isFlyKey, routeByMotion } from '../server/match/waves.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 { buildDeployMap, positionClass, canPlace, ownerRangeKeys, tileKey } from '../server/match/board.js';
|
||||||
import { computeBonds, bondSnapshot } from '../server/match/bondsMeta.js';
|
import { computeBonds, bondSnapshot } from '../server/match/bondsMeta.js';
|
||||||
import { Match } from '../server/match/Match.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 CHESS = Object.values(data.chess).sort((a, b) => byId(a.chessId, b.chessId));
|
||||||
const VISIBLE = CHESS.filter((c) => c.visible);
|
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 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 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, '')));
|
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 modeId = helpers === 1 ? 'mode_multi_normal' : 'mode_multi_hard';
|
||||||
const gd = gdFor(modeId);
|
const gd = gdFor(modeId);
|
||||||
const round = helpers === 1 ? 7 : 11;
|
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 seed = deriveSeed(20261005, `unite:${helpers}`);
|
||||||
const src = normalWave(gd, round, seed);
|
const src = normalWave(gd, round, seed);
|
||||||
const leakers = helpers === 1 ? ['p2', 'p3'] : ['p3', 'p4'];
|
const leakers = helpers === 1 ? ['p2', 'p3'] : ['p3', 'p4'];
|
||||||
@@ -601,7 +606,7 @@ export function fieldScenarios() {
|
|||||||
let uid = 1;
|
let uid = 1;
|
||||||
const players = [];
|
const players = [];
|
||||||
for (let h = 0; h < helpers; h++) {
|
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;
|
uid = t.nextUid;
|
||||||
// carried end state of the helper's own battle: HP ratio + SP, one knocked out, summons with SP only
|
// carried end state of the helper's own battle: HP ratio + SP, one knocked out, summons with SP only
|
||||||
t.units.forEach((u, k) => {
|
t.units.forEach((u, k) => {
|
||||||
|
|||||||
Reference in new issue
Block a user