feedback2 WD: 沼泽 triggers every second, 2 layers for an enemy of 重量 ≥ 3, operators get the ASPD part only (the skill's value 3 is that weight, not a 3 s interval)

This commit is contained in:
sganggs committed 2026-10-04 00:07:25 +08:00
1 parent 0907c3b7a4
commit 3502027ff2
6 files changed
+66 -18

No files matched your search

+1 -1
View File
@@ -1 +1 @@
{"act1autochess_m01":{"id":"act1autochess_m01","name":"战场#01","weight":50,"active":true,"modes":["mode_training_1","mode_single_normal","mode_single_funny","mode_multi_normal","mode_multi_funny"],"size":[19,21],"rows":["aaaaaaaaaa#aaaaaaaaaa","###IAAAAA###AAAAAI###","##Err###rrOrr###rrE##","###brbbbrfffrbbbrb###","###brbbbrfffrbbbrb###","###brrrrrfOfrrrrrb###","XXXXXXXXXXXXXXXXXXXXX","aaaaaaaaaa###########","####AAAAA##I#########","##Err###rrSrr###rrS##","###brbbbrf#brbbbrf###","###brbbbrf#brbbbrf###","###brrrrrfSbrrrrrfS##","XXXXXXXXXXXXXXXXXXXXX","#######fffffff#######","#######Sffffff#######","#######ppppppp#######","#######fffffff#######","#######Sffffff#######"],"tiles":{"a":{"tileKey":"tile_achand","height":"HIGH","buildable":"ALL","passable":"FLY_ONLY","groundPassable":false,"flyPassable":true,"special":null,"bb":{"isValidHand":1}},"#":{"tileKey":"tile_forbidden","height":"HIGH","buildable":"NONE","passable":"FLY_ONLY","groundPassable":false,"flyPassable":true,"special":null,"bb":{}},"I":{"tileKey":"tile_telin","height":"LOW","buildable":"NONE","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}},"A":{"tileKey":"tile_achand","height":"HIGH","buildable":"ALL","passable":"FLY_ONLY","groundPassable":false,"flyPassable":true,"special":null,"bb":{}},"E":{"tileKey":"tile_end","height":"LOW","buildable":"NONE","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}},"r":{"tileKey":"tile_road","height":"LOW","buildable":"ALL","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}},"O":{"tileKey":"tile_telout","height":"LOW","buildable":"NONE","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}},"b":{"tileKey":"tile_fence_bound","height":"LOW","buildable":"ALL","passable":"FLY_ONLY","groundPassable":false,"flyPassable":true,"special":null,"bb":{}},"f":{"tileKey":"tile_floor","height":"LOW","buildable":"NONE","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}},"X":{"tileKey":"tile_forbidden","height":"HIGH","buildable":"NONE","passable":"NONE","groundPassable":false,"flyPassable":false,"special":null,"bb":{}},"S":{"tileKey":"tile_start","height":"LOW","buildable":"NONE","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}},"p":{"tileKey":"tile_floor","height":"LOW","buildable":"NONE","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{"previewNotAlloed":1}}},"devices":[{"key":"trap_1112_acblzd","name":"盟约寒风","alias":null,"pos":[18,0],"dir":"UP","hidden":false,"role":"coldWind","stats":{"maxHp":100,"atk":0,"def":0,"res":0,"bat":1,"aspd":100,"blockCnt":0},"rangeGrid":null,"rangeTiles":null,"skill":{"skillId":"sktok_acblzd","name":"谢拉格寒风","level":1,"desc":"每隔一定时间对全场敌人造成一定时间的寒冷","descRaw":"每隔一定时间对全场敌人造成一定时间的<$ba.cold>寒冷</>","bb":{},"bbStr":{}},"desc":null,"active":true},{"key":"trap_1104_aclasert","name":"“双眼皮”","alias":"trap_1104_aclasert#1","pos":[9,7],"dir":"UP","hidden":true,"role":"turret","stats":{"maxHp":3000,"atk":900,"def":200,"res":0,"bat":4,"aspd":100,"blockCnt":0},"rangeGrid":[[1,0],[1,1],[1,2],[1,3],[1,4],[0,0],[0,1],[0,2],[0,3],[0,4],[-1,0],[-1,1],[-1,2],[-1,3],[-1,4]],"rangeTiles":[[9,6],[10,6],[11,6],[12,6],[13,6],[9,7],[10,7],[11,7],[12,7],[13,7],[9,8],[10,8],[11,8],[12,8],[13,8]],"skill":{"skillId":"sktok_aclasert","name":"“双眼皮”","level":1,"desc":"根据当前层数最高的盟约获得强化:每层盟约层数使自身攻击速度+1,攻击额外附带造成0.1%脆弱,持续2秒(至多300层)","descRaw":"根据当前层数最高的盟约获得强化:每层盟约层数使自身攻击速度<@ba.vup>+1</>,攻击额外附带造成<@ba.vup>0.1%</><$ba.fragile>脆弱</>,持续2秒(至多300层)","bb":{"attack_speed_per_stack":1,"damage_scale_per_stack":0.001,"max_attack_speed":300,"max_damage_scale":1.3},"bbStr":{}},"desc":null,"active":false},{"key":"trap_1104_aclasert","name":"“双眼皮”","alias":"trap_1104_aclasert#2","pos":[9,15],"dir":"UP","hidden":true,"role":"turret","stats":{"maxHp":3000,"atk":900,"def":200,"res":0,"bat":4,"aspd":100,"blockCnt":0},"rangeGrid":[[1,0],[1,1],[1,2],[1,3],[1,4],[0,0],[0,1],[0,2],[0,3],[0,4],[-1,0],[-1,1],[-1,2],[-1,3],[-1,4]],"rangeTiles":[[9,14],[10,14],[11,14],[12,14],[13,14],[9,15],[10,15],[11,15],[12,15],[13,15],[9,16],[10,16],[11,16],[12,16],[13,16]],"skill":{"skillId":"sktok_aclasert","name":"“双眼皮”","level":1,"desc":"根据当前层数最高的盟约获得强化:每层盟约层数使自身攻击速度+1,攻击额外附带造成0.1%脆弱,持续2秒(至多300层)","descRaw":"根据当前层数最高的盟约获得强化:每层盟约层数使自身攻击速度<@ba.vup>+1</>,攻击额外附带造成<@ba.vup>0.1%</><$ba.fragile>脆弱</>,持续2秒(至多300层)","bb":{"attack_speed_per_stack":1,"daLine truncated
{"act1autochess_m01":{"id":"act1autochess_m01","name":"战场#01","weight":50,"active":true,"modes":["mode_training_1","mode_single_normal","mode_single_funny","mode_multi_normal","mode_multi_funny"],"size":[19,21],"rows":["aaaaaaaaaa#aaaaaaaaaa","###IAAAAA###AAAAAI###","##Err###rrOrr###rrE##","###brbbbrfffrbbbrb###","###brbbbrfffrbbbrb###","###brrrrrfOfrrrrrb###","XXXXXXXXXXXXXXXXXXXXX","aaaaaaaaaa###########","####AAAAA##I#########","##Err###rrSrr###rrS##","###brbbbrf#brbbbrf###","###brbbbrf#brbbbrf###","###brrrrrfSbrrrrrfS##","XXXXXXXXXXXXXXXXXXXXX","#######fffffff#######","#######Sffffff#######","#######ppppppp#######","#######fffffff#######","#######Sffffff#######"],"tiles":{"a":{"tileKey":"tile_achand","height":"HIGH","buildable":"ALL","passable":"FLY_ONLY","groundPassable":false,"flyPassable":true,"special":null,"bb":{"isValidHand":1}},"#":{"tileKey":"tile_forbidden","height":"HIGH","buildable":"NONE","passable":"FLY_ONLY","groundPassable":false,"flyPassable":true,"special":null,"bb":{}},"I":{"tileKey":"tile_telin","height":"LOW","buildable":"NONE","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}},"A":{"tileKey":"tile_achand","height":"HIGH","buildable":"ALL","passable":"FLY_ONLY","groundPassable":false,"flyPassable":true,"special":null,"bb":{}},"E":{"tileKey":"tile_end","height":"LOW","buildable":"NONE","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}},"r":{"tileKey":"tile_road","height":"LOW","buildable":"ALL","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}},"O":{"tileKey":"tile_telout","height":"LOW","buildable":"NONE","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}},"b":{"tileKey":"tile_fence_bound","height":"LOW","buildable":"ALL","passable":"FLY_ONLY","groundPassable":false,"flyPassable":true,"special":null,"bb":{}},"f":{"tileKey":"tile_floor","height":"LOW","buildable":"NONE","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}},"X":{"tileKey":"tile_forbidden","height":"HIGH","buildable":"NONE","passable":"NONE","groundPassable":false,"flyPassable":false,"special":null,"bb":{}},"S":{"tileKey":"tile_start","height":"LOW","buildable":"NONE","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{}},"p":{"tileKey":"tile_floor","height":"LOW","buildable":"NONE","passable":"ALL","groundPassable":true,"flyPassable":true,"special":null,"bb":{"previewNotAlloed":1}}},"devices":[{"key":"trap_1112_acblzd","name":"盟约寒风","alias":null,"pos":[18,0],"dir":"UP","hidden":false,"role":"coldWind","stats":{"maxHp":100,"atk":0,"def":0,"res":0,"bat":1,"aspd":100,"blockCnt":0},"rangeGrid":null,"rangeTiles":null,"skill":{"skillId":"sktok_acblzd","name":"谢拉格寒风","level":1,"desc":"每隔一定时间对全场敌人造成一定时间的寒冷","descRaw":"每隔一定时间对全场敌人造成一定时间的<$ba.cold>寒冷</>","bb":{},"bbStr":{}},"desc":null,"active":true},{"key":"trap_1104_aclasert","name":"“双眼皮”","alias":"trap_1104_aclasert#1","pos":[9,7],"dir":"UP","hidden":true,"role":"turret","stats":{"maxHp":3000,"atk":900,"def":200,"res":0,"bat":4,"aspd":100,"blockCnt":0},"rangeGrid":[[1,0],[1,1],[1,2],[1,3],[1,4],[0,0],[0,1],[0,2],[0,3],[0,4],[-1,0],[-1,1],[-1,2],[-1,3],[-1,4]],"rangeTiles":[[9,6],[10,6],[11,6],[12,6],[13,6],[9,7],[10,7],[11,7],[12,7],[13,7],[9,8],[10,8],[11,8],[12,8],[13,8]],"skill":{"skillId":"sktok_aclasert","name":"“双眼皮”","level":1,"desc":"根据当前层数最高的盟约获得强化:每层盟约层数使自身攻击速度+1,攻击额外附带造成0.1%脆弱,持续2秒(至多300层)","descRaw":"根据当前层数最高的盟约获得强化:每层盟约层数使自身攻击速度<@ba.vup>+1</>,攻击额外附带造成<@ba.vup>0.1%</><$ba.fragile>脆弱</>,持续2秒(至多300层)","bb":{"attack_speed_per_stack":1,"damage_scale_per_stack":0.001,"max_attack_speed":300,"max_damage_scale":1.3},"bbStr":{}},"desc":null,"active":false},{"key":"trap_1104_aclasert","name":"“双眼皮”","alias":"trap_1104_aclasert#2","pos":[9,15],"dir":"UP","hidden":true,"role":"turret","stats":{"maxHp":3000,"atk":900,"def":200,"res":0,"bat":4,"aspd":100,"blockCnt":0},"rangeGrid":[[1,0],[1,1],[1,2],[1,3],[1,4],[0,0],[0,1],[0,2],[0,3],[0,4],[-1,0],[-1,1],[-1,2],[-1,3],[-1,4]],"rangeTiles":[[9,14],[10,14],[11,14],[12,14],[13,14],[9,15],[10,15],[11,15],[12,15],[13,15],[9,16],[10,16],[11,16],[12,16],[13,16]],"skill":{"skillId":"sktok_aclasert","name":"“双眼皮”","level":1,"desc":"根据当前层数最高的盟约获得强化:每层盟约层数使自身攻击速度+1,攻击额外附带造成0.1%脆弱,持续2秒(至多300层)","descRaw":"根据当前层数最高的盟约获得强化:每层盟约层数使自身攻击速度<@ba.vup>+1</>,攻击额外附带造成<@ba.vup>0.1%</><$ba.fragile>脆弱</>,持续2秒(至多300层)","bb":{"attack_speed_per_stack":1,"daLine truncated
+1 -1
View File
@@ -414,7 +414,7 @@ exactly (the builder warns if a multi-fragment template ever appears).
| `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) |
| `devices[]` | `{"key":"trap_013_blower","name":"源石流发生装置","alias":"trap_013_blower#001","pos":[13,5],"dir":"DOWN","hidden":false,"role":"blower","active":true,"stats":{…},"rangeGrid":[[0,0],…],"rangeTiles":[[13,5],[12,5],[11,5],[10,5]],"skill":{"skillId":"sktok_blower","bb":{"blower_s_character[equal].atk":0.3,…}},"desc":null}` | predefined devices; `role` ∈ crate, platform, mound, blower, mireController, tideController, turret (双眼皮, activated by band 机械援助), coldWind, sandstorm, waterPlatform, sealedFloor, bush, bossSpawn; `active` = present at match start (= not `hidden` in the level file); `rangeGrid` is facing right, `rangeTiles` = absolute tiles after rotating by `dir` (RIGHT `(dr,dc)`, UP `(dc,−dr)`, LEFT `(−dr,−dc)`, DOWN `(−dc,dr)`), clipped to the grid (null without a range). Terrain cards / band effects toggle devices by `alias` |
| `mapChars[]` | `{"key":"char_605_cmedic","alias":"char_605_cmedic#1","pos":[10,2],…}` | band_amedic map characters (→ `tokens.json`) |
| `special` | `{"mire":{"intervalSec":3,"aspdPerStack":-0.05,"moveMulPerStack":-0.05,"maxStacks":10,…}}`, `{"infection":{"bb":{"damage":70,"atk":0.2,"attack_speed":20,"duration":300}}}`, `{"deepsea":{"skillId":"sktok_tidectrl_3","bb":{…}}}`, `{"smog":{"rule":"operatorsNotTargetableByEnemyRanged"}}`, `{"blower":{…}}` | special-terrain parameters |
| `special` | `{"mire":{"intervalSec":1,"aspdPerStack":-0.05,"moveMulPerStack":-0.05,"maxStacks":10,"heavyWeight":3,…}}`, `{"infection":{"bb":{"damage":70,"atk":0.2,"attack_speed":20,"duration":300}}}`, `{"deepsea":{"skillId":"sktok_tidectrl_3","bb":{…}}}`, `{"smog":{"rule":"operatorsNotTargetableByEnemyRanged"}}`, `{"blower":{…}}` | special-terrain parameters |
| `runes[]`, `globalBuffs[]` | `{"key":"map_tile_blackb_assign","bb":{…},"bbStr":{…}}` | level runes (act2 m03 reed rune has no reed tiles — leftover) |
| `deployTiles` | `{"normal":{"melee":[[9,3],…],"rangedOnly":[[10,4],…],"changedByDevices":[…]},"bossLeft":{…},"bossRight":{…}}` | deployable tiles at match start: `melee` = LOW (effective) buildable ALL/MELEE (ranged may use them too) — and a 深水区 under an active 特制水上平台 (`waterPlatform`, "在水上建立可以部署任意单位的平台"; act1 m05 only — the prep's deploy map only: the sim does not model the canoes, so its Grid keeps those tiles NONE, automatic placements avoid them and the path tie-break counts them as non-blockable [ASSUMED, weight 0 this season]) —, `rangedOnly` = high ground / active platforms; tiles under active crates/mounds and the 深水区 excluded. `bossLeft` / `bossRight` (field coordinates, boss rows 1–5) are what a player may deploy on in a boss round's prep: board (r, c) = boss (r − 7, c) / the right half mirrored (r − 7, 20 − c) (server/match/board.js `field`, user playtest #5 item 7) |
| `groundPaths` / `groundPathsWithDevices` | `{"12,10->9,2":[[12,10],[12,9],…,[12,3],[11,3],[10,3],[9,3],[9,2]]}` | the ground route of the sim's own `server/sim/grid.js` flow field (research 08 §3.4: 4-direction SPFA from the goal, crates cost 1000, Bresenham line-of-sight smoothing — official route lengths — kept unless the remake's (0.1.0's) road-over-floor preference route crosses strictly fewer non-blockable floor / gate / 深水区 tiles — the fewest among equal-length chains, smoothing that never cuts across floor its grid route does not walk; a corner touch does not count as a crossing — e.g. 战场#01's lower gate takes the col-8 road, not the col-9 floor lane, while 战场#04's keeps the official diagonal from row 9 into row 10; DESIGN §14 corrections, test/sim/pathing-official.test.js) as the tiles an enemy crosses, over the whole map, without / with the match-start devices (crates cost 1000, platforms / mounds blocking [ASSUMED]); the battle re-reads its flow fields live |
+1 -1
View File
@@ -73,7 +73,7 @@ Boss board:
| `b` | fence_bound | LOW | ALL | no (fly only) | deployable, but ground enemies path around it |
| `a` / `A` | achand | HIGH | – | – | bench slot / non-bench hand tile |
| `S` `E` `I` `O` | start / end / telin / telout | LOW | NONE | yes | |
| `m` | mire | LOW | ALL | yes | +1 stack every 3 s: atk-speed −5, move −5 %; max 10; cleared on leaving |
| `m` | mire | LOW | ALL | yes | a trigger every 1 s on it (PRTS 沼泽控制; the first on entering): enemies +1 layer of atk-speed −5 / move −5 % (重量 ≥ 3, the skill's `value`: +2 layers), operators +1 layer of atk-speed −5 only; max 10; cleared on leaving |
| `g` | smog | LOW | ALL | yes | an operator here cannot be targeted by enemy **ranged** attacks |
| `d` | deepsea | LOW | ALL | yes | enemies take 40 dmg/s, atk-speed −60, move ×0.6 |
| `i` | infection | LOW | ALL | yes | allies on it and enemies crossing it get 70 true dmg/s, +20 % ATK, +20 ASPD for 300 s from their last contact (an enemy keeps it after leaving the tile; the tiles never switch off) |
+30 -10
View File
@@ -9,8 +9,10 @@
// RIGHT|DOWN|LEFT) equals the blower's dir / is its opposite / is perpendicular get ATK +
// blower_s_character[equal|opposite|vertical].atk (the m01 blowers blow DOWN); enemies moving with /
// against the flow get move speed × (1 + blower_s_enemy[equal|opposite].move_speed)
// 沼泽 m +1 stack on entering and every intervalSec s on the tile (max maxStacks): ASPD aspdPerStack (fractions
// are ×100 ASPD), move speed × (1 + moveMulPerStack × stacks); cleared on leaving (allies and ground enemies)
// 沼泽 m a unit on it (allies and ground enemies) triggers 【陷入沼泽】 on entering and every intervalSec (1) s
// there: an enemy gains 1 layer — 2 at 重量 ≥ heavyWeight (the device's `value`, 3) — of ASPD aspdPerStack
// (fractions are ×100 ASPD) and move speed × (1 + moveMulPerStack × layers), an operator 1 layer of the
// ASPD part only; at most maxStacks layers; cleared on leaving
// 烟雾 g operators on it cannot be targeted by enemy ranged attacks (stealth flag: blocked enemies still hit them)
// 深水 d ground enemies on it: sea_drown[enemy].damage true dmg/s, ASPD attack_speed (×100), move × move_speed
// 活性源石 i a unit on it (allies and ground enemies) gets a timed effect: damage true dmg/s, ATK + atk, ASPD +
@@ -46,7 +48,7 @@ const CRATE_KEY = 'trap_1105_accrate';
const TURRET_KEY = 'trap_1104_aclasert';
/** Research 05 §2.3 values, used only when a stage has no `special` block / device blackboard. */
const RESEARCH = Object.freeze({
mire: { intervalSec: 3, aspdPerStack: -0.05, moveMulPerStack: -0.05, maxStacks: 10 },
mire: { intervalSec: 1, aspdPerStack: -0.05, moveMulPerStack: -0.05, maxStacks: 10, heavyWeight: 3 },
deepsea: { damage: 40, attack_speed: -0.6, move_speed: 0.6 },
infection: { damage: 70, atk: 0.2, attack_speed: 20, duration: 300 },
});
@@ -288,11 +290,14 @@ function buildTerrain(battle, st) {
// mire
const m = sp.mire || {};
const mdev = devSkill('mireController') || {};
// the device skill's `value` (3) is the 重量 from which an enemy gains 2 layers, not an interval (PRTS 沼泽控制)
const mInterval = num(m.intervalSec, RESEARCH.mire.intervalSec);
st.mire = {
interval: num(m.intervalSec ?? mdev.value, RESEARCH.mire.intervalSec),
interval: mInterval > 0 ? mInterval : RESEARCH.mire.intervalSec,
aspdPer: aspdOf(num(m.aspdPerStack ?? mdev.attack_speed, RESEARCH.mire.aspdPerStack)),
movePer: num(m.moveMulPerStack ?? mdev.move_speed, RESEARCH.mire.moveMulPerStack),
max: Math.max(1, Math.floor(num(m.maxStacks ?? mdev.max_stack_cnt, RESEARCH.mire.maxStacks))),
heavyWeight: num(m.heavyWeight ?? mdev.value, RESEARCH.mire.heavyWeight),
};
// deep sea
const ds = sp.deepsea?.bb || devSkill('tideController') || {};
@@ -340,13 +345,14 @@ function enterTerrain(battle, st, u, code) {
m.terrain = code;
m.terrainSince = battle.time;
m.mireStacks = 0;
m.mireTriggers = 0;
if (!code) return;
if (code === TERRAIN.smog) battle.addBuff(u, { key: BUFF[code], flags: { stealth: true } });
else if (code === TERRAIN.deepsea) {
const D = st.deepsea;
battle.addBuff(u, { key: BUFF[code], mods: { aspd: D.aspd, moveMul: D.moveMul }, interval: 1, onTick: terrainDamage(battle, D.damage) });
}
// mire stacks are applied by tickMire, 活性源石 by touchInfection (every tick on the tile)
// mire layers are applied by tickMire, 活性源石 by touchInfection (every tick on the tile)
}
/**
@@ -371,12 +377,26 @@ function touchInfection(battle, st, u) {
battle.addBuff(u, { key, duration: I.duration, mods: { atkPct: I.atk, aspd: I.aspd }, interval: 1, onTick: terrainDamage(battle, I.damage) });
}
/** Layers an enemy gains per 【陷入沼泽】 trigger at 重量 ≥ heavyWeight (PRTS 沼泽控制: "若其重量大于等于3,改为获得2层"). */
const MIRE_HEAVY_LAYERS = 2;
/**
* 沼泽 (PRTS 沼泽控制 备注): a unit in the mire triggers its 【陷入沼泽】 "每秒…一次" (the device skill charges in 1 s) — an
* enemy gains 1 layer of ASPD −5 % and move speed −5 % (2 layers at 重量 ≥ heavyWeight, the skill's `value` 3), any other
* unit 1 layer of ASPD −5 % only; at most 10 layers; "上述减益于单位不再位于沼泽之中时解除" (enterTerrain).
* [ASSUMED] the first trigger comes on entering, then one every second.
*/
function tickMire(battle, st, u) {
const M = st.mire;
const n = Math.min(M.max, 1 + Math.floor((battle.time - u.mem.terrainSince + 1e-9) / M.interval));
if (n === u.mem.mireStacks) return;
u.mem.mireStacks = n;
battle.addBuff(u, { key: BUFF[TERRAIN.mire], refresh: 'replace', mods: { aspd: M.aspdPer * n, moveMul: Math.max(0, 1 + M.movePer * n) } });
const M = st.mire, m = u.mem;
const due = 1 + Math.floor((battle.time - m.terrainSince + 1e-9) / M.interval);
if (due <= m.mireTriggers) return;
const enemy = u.side === 'enemy';
const per = enemy && num(u.s.massLevel, 0) >= M.heavyWeight ? MIRE_HEAVY_LAYERS : 1;
const n = Math.min(M.max, m.mireStacks + per * (due - m.mireTriggers));
m.mireTriggers = due;
if (n === m.mireStacks) return;
m.mireStacks = n;
battle.addBuff(u, { key: BUFF[TERRAIN.mire], refresh: 'replace', mods: enemy ? { aspd: M.aspdPer * n, moveMul: Math.max(0, 1 + M.movePer * n) } : { aspd: M.aspdPer * n } });
}
/**
+27 -4
View File
@@ -608,17 +608,40 @@ test('气流 (act2 m01 blowers): enemies moving with the flow ×(1+equal), again
assert.ok(up.has(Math.round((1 + bb['blower_s_enemy[opposite].move_speed']) * 100) / 100), `against the flow ${[...up]}`);
});
test('沼泽 (act2 m02): +1 stack on entering and every intervalSec (ASPD −5, move −5 % each), max stacks, cleared on leaving', REAL, () => {
test('沼泽 (act2 m02): a trigger on entering and every second — 1 layer (an enemy of 重量 ≥ 3: 2) of ASPD −5 / move −5 %, at most 10; operators ASPD only; cleared on leaving', REAL, () => {
const mire = ds.getStage('act2autochess_m02').special.mire;
// PRTS 沼泽控制: "每秒将会触发一次" (sktok_mire spData maxChargeTime 1); "若其重量大于等于3,改为获得2层" (the skill's value 3)
assert.equal(mire.intervalSec, 1);
assert.equal(mire.heavyWeight, 3);
const h = makeBattle({ stageId: 'act2autochess_m02', defs: { chess: { test_guard: guard() } }, units: [{ chessId: 'test_guard', row: 11, col: 7 }], autoFinish: false, timeLimit: 60 });
h.step(2);
assert.equal(terrainAt(h.b, 11, 7), 'mire');
const g = h.unit('test_guard');
approx(g.s.aspd, 100 + mire.aspdPerStack * 100, 1e-9, '1 stack');
approx(g.s.aspd, 100 + mire.aspdPerStack * 100, 1e-9, '1 layer');
assert.equal(g.findBuff('terrain:mire').mods.moveMul, undefined, 'an operator gets the ASPD part only');
h.run(mire.intervalSec);
approx(g.s.aspd, 100 + 2 * mire.aspdPerStack * 100, 1e-9, '2 stacks');
approx(g.s.aspd, 100 + 2 * mire.aspdPerStack * 100, 1e-9, '2 layers');
h.run(mire.intervalSec * mire.maxStacks);
approx(g.s.aspd, 100 + mire.maxStacks * mire.aspdPerStack * 100, 1e-9, 'max stacks');
approx(g.s.aspd, 100 + mire.maxStacks * mire.aspdPerStack * 100, 1e-9, 'max layers');
// enemies standing in the mire: 重量 1 gains 1 layer a second, 重量 3 two (heavyWeight), both capped at maxStacks
const layersOver = (mass) => {
const key = `enemy_mass${mass}`;
const h3 = makeBattle({ stageId: 'act2autochess_m02', defs: { enemies: { [key]: walker({ key, mass }) } }, enemies: [{ key, pos: [10, 7], route: { motion: 'WALK', start: [10, 7], end: [10, 7], checkpoints: [{ type: 'WAIT', time: 99 }] } }], autoFinish: false, timeLimit: 60 });
h3.step();
const e = h3.enemy(key);
assert.equal(terrainAt(h3.b, 10, 7), 'mire');
const t0 = e.mem.terrainSince;
const out = [0.5, 1.5, 2.5, 4.5, 9.5, 20].map((t) => {
h3.run(t0 + t - h3.b.time);
const n = Math.round((e.s.aspd - 100) / (mire.aspdPerStack * 100));
approx(e.s.moveSpeed, 1 + n * mire.moveMulPerStack, 1e-9, `move ×(1 − 5 % × ${n})`);
return n;
});
checkInvariants(h3.b);
return out;
};
assert.deepEqual(layersOver(1), [1, 2, 3, 5, 10, 10], 'weight 1: a layer per second');
assert.deepEqual(layersOver(3), [2, 4, 6, 10, 10, 10], 'weight 3: two layers per second');
// an enemy walking down col 7 (mire) is slowed there and recovers once it leaves
const h2 = makeBattle({ stageId: 'act2autochess_m02', defs: { enemies: { enemy_walker: walker() } }, enemies: [{ key: 'enemy_walker', route: { motion: 'WALK', start: [12, 7], end: [9, 2], checkpoints: [] } }], autoFinish: false, timeLimit: 60 });
let slowed = false;
+6 -1
View File
@@ -2219,7 +2219,12 @@ function buildStages(ctx, modesById) {
const byKey = (k) => devices.find((d) => d.key === k);
if (rows.some((l) => l.includes('m'))) {
const d = byKey('trap_098_mire');
special.mire = { source: d ? d.key : null, intervalSec: d?.skill?.bb?.value ?? 3, aspdPerStack: d?.skill?.bb?.attack_speed ?? -0.05, moveMulPerStack: d?.skill?.bb?.move_speed ?? -0.05, maxStacks: d?.skill?.bb?.max_stack_cnt ?? 10, clearedOnLeave: true };
// PRTS 沼泽控制: a unit in the mire triggers 【陷入沼泽】 "每秒…一次" (the device skill's charge time, spData
// maxChargeTime 1) and an enemy "若其重量大于等于3,改为获得2层" — the skill's `value` (3) is that 重量, not an
// interval; "上述减益于单位不再位于沼泽之中时解除" (clearedOnLeave)
const mireSk = d?.skill ? ctx.skillTable[d.skill.skillId]?.levels?.[Math.max(0, (d.skill.level || 1) - 1)] : null;
const charge = mireSk?.spData?.maxChargeTime;
special.mire = { source: d ? d.key : null, intervalSec: typeof charge === 'number' && charge > 0 ? charge : 1, aspdPerStack: d?.skill?.bb?.attack_speed ?? -0.05, moveMulPerStack: d?.skill?.bb?.move_speed ?? -0.05, maxStacks: d?.skill?.bb?.max_stack_cnt ?? 10, heavyWeight: d?.skill?.bb?.value ?? 3, clearedOnLeave: true };
}
if (rows.some((l) => l.includes('d'))) {
const d = byKey('trap_042_tidectrl');