From 4304eb057c5cb7c139bd4d35ec9d0d2a56c09b93 Mon Sep 17 00:00:00 2001 From: sganggs Date: Mon, 5 Oct 2026 19:47:12 +0800 Subject: [PATCH] =?UTF-8?q?feedback5=20WJ:=20=E4=BC=BA=E5=A4=9C's=20?= =?UTF-8?q?=E7=8B=BC=E7=BE=A4=20takes=20its=20official=20=E6=88=98?= =?UTF-8?q?=E6=9C=AF=E7=82=B9=E5=BD=A2=E6=80=81=20and=20S1=20=E9=A2=86?= =?UTF-8?q?=E8=A2=96=E7=9A=84=E5=91=BC=E5=94=A4=20acts=20by=20the=20pack's?= =?UTF-8?q?=20state=20(PRTS=20=E7=8B=BC=E7=BE=A4=E9=A2=86=E8=A2=96=20/=20S?= =?UTF-8?q?1=20=E5=A4=87=E6=B3=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fatal hit on the last 狼影, or a 撤退 while 伺夜 stands, no longer kills the pack. It enters its 战术点形态, the remake's knocked-out piece: off the fight (no block, no attack, not targetable), kept, its tile reserved, 0 狼影. After the 狼影 recovery time it comes back on its tile at full HP with 1 狼影 and a fresh 狼影 cycle. That time is the token talent's interval, read from the data: 25 s on both chess. Until now it came back after the token's 10 s redeploy time with 2 狼影. Both packs share this (tokens.js installWolfTacticalPoint): the prep-placed 狼群 piece and the 伺夜 kit's own pack. - A return timer carries the form's seq and a return cancels it, so a stale timer never revives the pack early. - When 伺夜 leaves, a standing pack still leaves with him ('expired', no form). A pack in its form ends it with no return; his redeploy brings a fresh pack. S1 casts when ready while the pack is on the field in either form, and always pays +cost DP: ① in its 战术点形态 the pack comes back at once with 1 狼影 and a fresh cycle; ② below the cap, +1 狼影, the pack's own cycle untouched; ③ at the cap, the pack's HP is reset to max. S2 still needs a standing pack: its 备注 says nothing of the form [ASSUMED]. A held S2 gift stays on the pack through the form [ASSUMED]. fx: the kit's pack keeps 'summon' / 'revive', the piece keeps 'wolfShadow' / 'wolfShadowLost'. Entering the form shows 'wolfShadowLost' with count 0. No new kinds, no client change. Data unchanged. Docs: DESIGN §5.6 (SP_FULL note), PLAYING (作战中, 自动触发), SIM (token pieces), the tokens.js / kit comments. Tests: - kits_alt_t3, new: 3_19 战术点形态 (both chess × both packs), S1 ①②③ with the stale-timer guard, 撤退 / 伺夜 leaving. - kits_alt_t3, rewritten: the S1 no-pack test, and the S2 auto-cast test (no cast in the form, a cast once the pack is back). - kits_t3 3_19 wolf pack and tokens_devices 狼群 board piece: the form, then a return with 1 狼影. Golden (each fields 伺夜 with his pack knocked out at its last 狼影 while he stands: 25 s out and 1 狼影 instead of 10 s and 2): - roster-007 and roster-009: the fights change. - roster-028 (elite S1): S1 brings the pack back at once. - roster-026 and roster-040: 伺夜 falls before the old return; only the new 'wolfShadowLost' fx moves. - bond-siracusaShip-high. - matches solo-NORMAL-1, coop2-NORMAL-3, coop3-ABYSS-5, coop2-NORMAL-8-serverrun: the bots field 伺夜. --- docs/DESIGN.md | 2 +- docs/PLAYING.md | 4 +- docs/SIM.md | 5 +- .../content/kits/ops/chess_char_3_19-vigil.js | 103 ++++++--- server/sim/content/tokens.js | 136 +++++++++++- test/content/kits_alt_t3.test.js | 204 ++++++++++++++++-- test/content/kits_t3.test.js | 8 +- test/content/tokens_devices.test.js | 20 +- test/golden/bonds.json | 30 +-- test/golden/matches.json | 138 ++++++------ test/golden/roster.json | 149 ++++++------- 11 files changed, 577 insertions(+), 222 deletions(-) diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 57211fcf..c41b4512 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -305,7 +305,7 @@ Engine helpers (content must use these, never touch internals): `battle.dealDama SP: `spType` `INCREASE_WITH_TIME` (+`spRecovery`/s), `INCREASE_WHEN_ATTACK` (+1 per attack), `INCREASE_WHEN_TAKEN_DAMAGE` (+1 per hit taken); starts at `initSp`; no SP gain while a duration skill is active. `maxChargeTime > 1` ⇒ charges. -Auto-trigger rules (`skillTrigger.rule` in data — the official 技能策略, PRTS 卫戍协议/帮助 §作战阶段 技能操作; §20.2): `DEFAULT` (SP ready AND about to attack/heal AND an enemy/injured ally inside the **initial** range, or an enemy the unit blocks; with `trigger.allies` + `hpAtMost` an ally of the skill grid at or below that HP ratio — 塞雷娅 S1 急救, whose cast replaces that attack and is withdrawn with its charge if the ally recovers first), `SKILL_RANGE` (a MANUAL skill with a 技能范围 of its own, not an attack-range change: any living enemy, stealthed ones included, on the skill range, checked every tick — "仅在技能范围内存在敌人(无视其不可选中)时释放技能"; 13 skills / 26 records by the official rule, 14 / 28 in the data with 余 S2's deviation, §22.10), `ACTIVE_RANGE` (the owner's deliberate deviation of 2026-10-05, 0.2.0: a MANUAL skill on the basic strategy — or, by the owner's further decisions of the same day, on the `SEARCH` row, and 深巡 S2 on top of its §21.29 DEFAULT — whose attack range while it runs strictly contains the operator's own range — its rangeId grid, or the own range grown by `ability_range_forward_extend` — checks the DEFAULT condition on that running range every tick, no attack needed; `customRangeGrid` = the running range, `rawRule` the official row (DEFAULT, SEARCH, 深巡's TAKE_DAMAGE); 39 skills / 78 records, of them 薄绿 S1, 蜜蜡 S1, 卡涅利安 S3, 玛恩纳 S2 and 安洁莉娜 S3 from SEARCH; 烛煌 S2 / S3, whose ranges do not contain her 3-1, keep DEFAULT), `TAKE_DAMAGE` (on taking damage — any damage, 无来源 included, never a 流失; every MANUAL 重装 skill, §21.12 — but the six of the deliberate deviation, §21.29: 深巡 / 雷蛇 S2, 号角 S2 / S3 and 灰毫 S1 / S2 are `DEFAULT`, `rawRule` keeping the official TAKE_DAMAGE — 深巡 S2 then `ACTIVE_RANGE` on its 3-2; and 余 S2, `SKILL_RANGE` on its x-1 (§22.10)), `SP_FULL` (immediately — 执旗手 / 战术家 / 吟游者 MANUAL skills, AUTO DP skills such as 焰尾 S1, 凛御银灰 S1 (伺夜 S1 / S2 too, but only while his pack is on the field — PRTS 备注 「仅场上存在狼群时可触发技能」, the kit's own cast; the owner's decision of 2026-10-05 for S1), and, by their kits, AUTO skills whose effects act on allies only: 引星棘刺 S1, 浊心斯卡蒂 S2, 纯烬艾雅法拉 S1 — the owner's decision of 2026-10-05 for the last), `CUSTOM_RANGE` (enemy inside the custom grid), `SEARCH` (an enemy inside the initial range, checked every tick — no attack needed; 解放者 / 阵法术师 MANUAL skills and 安洁莉娜 S2, those without a larger running range), `GDGLOW_SKILL_2` (a valid target anywhere on the field: 荒芜拉普兰德, 纯烬艾雅法拉 S3), special ids. The class rows cover **every MANUAL skill** of the class and never an AUTO skill (AUTO skills keep their own rule — an inference: PRTS names whole classes; the data's `skillIndex 0` / `-1` sentinel argues the other way, §20.2). **Operation cooldown**: the engine auto-casts a MANUAL skill no sooner than `AUTO_OP_COOLDOWN` (3 s) after its previous cast and after the unit's battle-start deployment ("自动操作具有3s冷却,在完成一次操作或作战开始时部署的单位将进入冷却"; `Battle._deploy` initial, `battle.flags.startOpCooldown`); kits with an automatic cast of their own check `skill.opCooling`; a pending "next attack" never takes a second charge. PASSIVE skills are always on. +Auto-trigger rules (`skillTrigger.rule` in data — the official 技能策略, PRTS 卫戍协议/帮助 §作战阶段 技能操作; §20.2): `DEFAULT` (SP ready AND about to attack/heal AND an enemy/injured ally inside the **initial** range, or an enemy the unit blocks; with `trigger.allies` + `hpAtMost` an ally of the skill grid at or below that HP ratio — 塞雷娅 S1 急救, whose cast replaces that attack and is withdrawn with its charge if the ally recovers first), `SKILL_RANGE` (a MANUAL skill with a 技能范围 of its own, not an attack-range change: any living enemy, stealthed ones included, on the skill range, checked every tick — "仅在技能范围内存在敌人(无视其不可选中)时释放技能"; 13 skills / 26 records by the official rule, 14 / 28 in the data with 余 S2's deviation, §22.10), `ACTIVE_RANGE` (the owner's deliberate deviation of 2026-10-05, 0.2.0: a MANUAL skill on the basic strategy — or, by the owner's further decisions of the same day, on the `SEARCH` row, and 深巡 S2 on top of its §21.29 DEFAULT — whose attack range while it runs strictly contains the operator's own range — its rangeId grid, or the own range grown by `ability_range_forward_extend` — checks the DEFAULT condition on that running range every tick, no attack needed; `customRangeGrid` = the running range, `rawRule` the official row (DEFAULT, SEARCH, 深巡's TAKE_DAMAGE); 39 skills / 78 records, of them 薄绿 S1, 蜜蜡 S1, 卡涅利安 S3, 玛恩纳 S2 and 安洁莉娜 S3 from SEARCH; 烛煌 S2 / S3, whose ranges do not contain her 3-1, keep DEFAULT), `TAKE_DAMAGE` (on taking damage — any damage, 无来源 included, never a 流失; every MANUAL 重装 skill, §21.12 — but the six of the deliberate deviation, §21.29: 深巡 / 雷蛇 S2, 号角 S2 / S3 and 灰毫 S1 / S2 are `DEFAULT`, `rawRule` keeping the official TAKE_DAMAGE — 深巡 S2 then `ACTIVE_RANGE` on its 3-2; and 余 S2, `SKILL_RANGE` on its x-1 (§22.10)), `SP_FULL` (immediately — 执旗手 / 战术家 / 吟游者 MANUAL skills, AUTO DP skills such as 焰尾 S1, 凛御银灰 S1 (伺夜 S1 / S2 too, but only while his pack is on the field — PRTS 备注 「仅场上存在狼群时可触发技能」, the kit's own cast; the owner's decision of 2026-10-05 for S1; S1 also while the pack sits in its 战术点形态, which the cast ends — S2 waits for it to come back [ASSUMED]), and, by their kits, AUTO skills whose effects act on allies only: 引星棘刺 S1, 浊心斯卡蒂 S2, 纯烬艾雅法拉 S1 — the owner's decision of 2026-10-05 for the last), `CUSTOM_RANGE` (enemy inside the custom grid), `SEARCH` (an enemy inside the initial range, checked every tick — no attack needed; 解放者 / 阵法术师 MANUAL skills and 安洁莉娜 S2, those without a larger running range), `GDGLOW_SKILL_2` (a valid target anywhere on the field: 荒芜拉普兰德, 纯烬艾雅法拉 S3), special ids. The class rows cover **every MANUAL skill** of the class and never an AUTO skill (AUTO skills keep their own rule — an inference: PRTS names whole classes; the data's `skillIndex 0` / `-1` sentinel argues the other way, §20.2). **Operation cooldown**: the engine auto-casts a MANUAL skill no sooner than `AUTO_OP_COOLDOWN` (3 s) after its previous cast and after the unit's battle-start deployment ("自动操作具有3s冷却,在完成一次操作或作战开始时部署的单位将进入冷却"; `Battle._deploy` initial, `battle.flags.startOpCooldown`); kits with an automatic cast of their own check `skill.opCooling`; a pending "next attack" never takes a second charge. PASSIVE skills are always on. **SkillSpec** (authored per chess in `sim/content/kits/tierN.js`; values come from the chess's skill blackboard at its level — normal Lv4, elite Lv7 — so a spec is a *function of the blackboard*): ```js diff --git a/docs/PLAYING.md b/docs/PLAYING.md index 70de41fa..d1b1ec67 100644 --- a/docs/PLAYING.md +++ b/docs/PLAYING.md @@ -68,10 +68,10 @@ - 拖动时模型一直在指针 / 手指下(脚在指针下方约半格,鼠标和触屏一样),**指针所在的格子**就是落点并高亮;装备拖到哪个格子,就配发给那个格子上的干员;在商店栏上松手则放回原位。 - 点击干员显示攻击范围和底部按钮(「撤退」回到整备区、「出售 +1」);右键 / 长按查看详细信息:头像旁是盟约,下面第一项就是干员的**特质**(如「休整期结束时 …」),之后是特性、属性、攻击范围、技能、模组、装备、天赋。属性是**实时数值**:作战中每秒刷新 4 次(当前生命、攻击、防御、法抗、攻击间隔、阻挡),休整期显示棋盘上的干员**开战时**的数值(已算入装备、盟约层数、特质、策略与机变效果);高于基础值为绿色、低于为红色并标出差值(攻击间隔变短算增益),鼠标悬停可看基础值。 - **召唤物手动摆放**:把带可部署召唤物的干员放上棋盘后,它的召唤物会进入整备区(赫默的医疗探机、巫恋的诅咒娃娃、凯瑟琳的 2 个支援装置、浊心斯卡蒂的海嗣、伺夜的狼群、缪尔赛思的流形;同一干员的召唤物叠在一格,不占部署位),像干员一样拖到棋盘上并选方向。凯瑟琳的支援装置给**它面朝的那一格**上的干员加屏障,所以要放在干员旁边、朝向干员。**战术家的援军**(伺夜的狼群、缪尔赛思的流形,也就是选战术点)和官方一样**只能放在召唤者的攻击范围内**:拖动时只有这些格子会亮起;之后在原地给战术家换朝向时,不在新攻击范围里的援军会退回整备区(还在范围里的保持不动;整备区和临时整备区都满、援军无处可退时,这次换朝向不生效并提示「整备区已满」)。撤退或出售干员时它的召唤物一起移除;**移动干员到别的格子时(包括把召唤物拖到干员身上交换位置),它已摆放的召唤物会退回整备区**,需要重新摆放。整备区满时退回的召唤物进入临时整备区,休整期结束还没处理的会在下一回合重新回到整备区。 -- 作战中:海嗣、狼群、流形、支援装置开战时就在摆放的位置部署;医疗探机、诅咒娃娃开战时也在摆放的位置免费部署一次(持续 10 / 15 秒,和官方一样不需要先开技能),之后所属干员每次发动技能时再次在原位出现;所属干员被击倒、再部署回场时,技能给的那一个如果还没出现,会立刻出现。巫恋退场时诅咒娃娃随之撤退,赫默退场时已出现的医疗探机不受影响。医疗探机、诅咒娃娃、支援装置、海嗣没有摆放就不会出现;狼群、流形(战术家的援军)没有摆放时会在战术点出现。 +- 作战中:海嗣、狼群、流形、支援装置开战时就在摆放的位置部署;医疗探机、诅咒娃娃开战时也在摆放的位置免费部署一次(持续 10 / 15 秒,和官方一样不需要先开技能),之后所属干员每次发动技能时再次在原位出现;所属干员被击倒、再部署回场时,技能给的那一个如果还没出现,会立刻出现。巫恋退场时诅咒娃娃随之撤退,赫默退场时已出现的医疗探机不受影响。医疗探机、诅咒娃娃、支援装置、海嗣没有摆放就不会出现;狼群、流形(战术家的援军)没有摆放时会在战术点出现。狼群最后一只狼影被击倒后进入战术点形态:暂时离开战斗(不阻挡、不攻击、不会被攻击,格子仍然占着),经过狼影的恢复时间(25 秒)后在原地满血、带着 1 只狼影回来,之后重新开始每 25 秒加 1 只狼影;伺夜退场时狼群随之撤退,伺夜再部署回场时狼群重新带着 2 只狼影出现。 - **阻挡**看接触:干员阻挡中心距离约 0.7 格以内、与它接触的敌人(地面干员挡不住飞行和近地悬浮的敌人)。敌人卡在两名干员之间时,阻挡它的干员倒下、撤退或被晕眩后,另一名仍与它接触、还有空余阻挡数的干员会**立刻接替阻挡**;都没有空位时敌人才会走掉。迎面走来的敌人会停在干员格子前面。**阻挡了就一定能打到**:任何干员(包括站在近战位的远程干员和召唤物)总能攻击自己阻挡的敌人,不管它在不在攻击范围内、在干员哪一侧,并且优先攻击它(医疗干员除外:咒愈师以外的医疗挡住敌人时照常治疗队友,不会攻击它;「停止攻击」类技能期间也除外;技能原文另有限制的也按原文:普罗旺斯二技能开启时不以生命值高于 80% 的敌人为目标,寒檀二技能的冰凌随机落在攻击范围内的地块上)。 - 开战时干员按**逐列从左到右、每列从上到下**的顺序部署(最左一列最先,与阿戈尔吞噬「更靠左和靠上」的顺序相同),召唤物在所有干员之后。被阻挡的敌人先打阻挡它的干员;没被阻挡的远程敌人先打嘲讽等级高的,同级时打最后部署的(召唤物、最右一列靠下的干员和刚再部署的干员更容易挨打)。隐匿的干员只会被它阻挡的敌人攻击,没被它挡住的敌人的溅射、爆炸、范围技能、留下的伤害区域(包括假想敌:蚀裂被击倒后喷出的毒雾)以及深池伙友卫队、扎罗的减攻速光环也落不到它身上(和官方一样;写明「无视无法选择」的效果如污染秽蚀照常能打到,寒霜的减攻速光环和“墓碑”这类地图效果照常生效);**迷彩**(如忍冬三技能期间击倒敌人后、寒芒克洛丝一技能)同样只会被它阻挡的敌人攻击,也显示为半透明,但躲不开溅射、爆炸和范围伤害(同时打范围内所有单位的普通攻击——如“斩胄之剑”“破胄之锤”“灵幛”——打不到它),也不算隐匿(不触发叙拉古等「隐匿时」效果)。叙拉古的隐匿按时(32 秒 + 每层 0.4 秒)结束;忍冬的迷彩按官方持续到她下一次开技能。**蒂比起飞**期间只挡飞行敌人,地面敌人不会选她为目标(普通攻击、溅射、技能范围、燃烧区域和要选中目标的减益都落不到她身上);飞行和悬浮敌人、写明「无视无法选择」的效果(如污染秽蚀)照常能打到她;地面敌人的光环和全场效果(如“墓碑”让全场攻击力、防御力减半)、她攻击敌人时招来的反制(如重装侦察兵的暴露)照常生效,起飞前已挂上的持续伤害也照常跳。 -- **技能自动释放**按官方「技能策略」:一般的手动技能在技能就绪、即将攻击 / 治疗且初始攻击范围内有敌人(治疗技能:有受伤的队友)时释放;**带独立技能范围**的技能(如德克萨斯「剑雨」、凛御银灰「御敌的锋锐」、锏「归于宁静」,不含「攻击范围扩大」类)在**技能范围内出现敌人**时就释放,不必先攻击;**开启后攻击范围比平时大**的手动技能(如莫斯提马「序时之匙」、银灰「真银斩」、史尔特尔「黄昏」、白面鸮「脑啡肽」)在**开启后的攻击范围内出现敌人**(治疗技能:有受伤的队友)时就释放,不必等敌人走进平时的攻击范围,这是本作按玩家反馈有意与官方不同的地方;**重装干员**的手动技能在受到伤害时释放(例外:深巡、雷蛇的二技能,号角的二、三技能,灰毫的一、二技能按玩家反馈改为攻击范围内有敌人时就释放(深巡二技能按开启后更远的攻击范围判断),余的二技能「厚礼上宾」改为周围两格(技能范围)内有敌人时就释放,这是本作有意与官方不同的地方;深巡一技能仍要受到伤害);执旗手 / 战术家 / 吟游者技能就绪即释放,解放者 / 阵法术师在初始攻击范围内有敌人时释放(开启后攻击范围扩大的技能——薄绿「风语」、蜜蜡「沙暴扩散」、卡涅利安「食噬之印」、玛恩纳「未宽解的悲哀」,以及安洁莉娜「秘杖·反重力模式」——同样按开启后的攻击范围判断)。**自动触发**的技能按其本身规则:只作用于队友的(如引星棘刺「度算浪波」、浊心斯卡蒂「同葬无光之愿」、纯烬艾雅法拉「无声润物」)技力满就释放,不用等敌人或伤员;伺夜「领袖的呼唤」「领袖的馈赠」只在场上有狼群时释放(狼群被击倒后要等它重新出现);又如古米「备用军粮」在身边(技能范围内)有受伤的队友时就会发动,下一次攻击改为治疗该队友(不用先接敌;她的二技能「食粮烹制」是手动技能,按重装规则在受到伤害时释放);塞雷娅「急救」在她即将攻击时、身边有生命不高于一半的队友才发动,这次攻击直接改为治疗;新约能天使二技能的护盾按官方给攻击范围内**仇恨最高**的队友(嘲讽等级高的优先,同级时给最后部署的),不一定是站在最前面挨打的那个。手动技能的自动释放有 3 秒冷却:开战部署后先等 3 秒,之后同一干员两次自动释放至少间隔 3 秒(可充能的技能不会同时放完)。 +- **技能自动释放**按官方「技能策略」:一般的手动技能在技能就绪、即将攻击 / 治疗且初始攻击范围内有敌人(治疗技能:有受伤的队友)时释放;**带独立技能范围**的技能(如德克萨斯「剑雨」、凛御银灰「御敌的锋锐」、锏「归于宁静」,不含「攻击范围扩大」类)在**技能范围内出现敌人**时就释放,不必先攻击;**开启后攻击范围比平时大**的手动技能(如莫斯提马「序时之匙」、银灰「真银斩」、史尔特尔「黄昏」、白面鸮「脑啡肽」)在**开启后的攻击范围内出现敌人**(治疗技能:有受伤的队友)时就释放,不必等敌人走进平时的攻击范围,这是本作按玩家反馈有意与官方不同的地方;**重装干员**的手动技能在受到伤害时释放(例外:深巡、雷蛇的二技能,号角的二、三技能,灰毫的一、二技能按玩家反馈改为攻击范围内有敌人时就释放(深巡二技能按开启后更远的攻击范围判断),余的二技能「厚礼上宾」改为周围两格(技能范围)内有敌人时就释放,这是本作有意与官方不同的地方;深巡一技能仍要受到伤害);执旗手 / 战术家 / 吟游者技能就绪即释放,解放者 / 阵法术师在初始攻击范围内有敌人时释放(开启后攻击范围扩大的技能——薄绿「风语」、蜜蜡「沙暴扩散」、卡涅利安「食噬之印」、玛恩纳「未宽解的悲哀」,以及安洁莉娜「秘杖·反重力模式」——同样按开启后的攻击范围判断)。**自动触发**的技能按其本身规则:只作用于队友的(如引星棘刺「度算浪波」、浊心斯卡蒂「同葬无光之愿」、纯烬艾雅法拉「无声润物」)技力满就释放,不用等敌人或伤员;伺夜「领袖的呼唤」「领袖的馈赠」只在场上有狼群时释放:狼群处于战术点形态时,「领袖的呼唤」也会释放并让它立刻带着 1 只狼影回到场上(狼影未满时改为加 1 只狼影,已满时把狼群的生命值回满),「领袖的馈赠」则要等狼群回来;又如古米「备用军粮」在身边(技能范围内)有受伤的队友时就会发动,下一次攻击改为治疗该队友(不用先接敌;她的二技能「食粮烹制」是手动技能,按重装规则在受到伤害时释放);塞雷娅「急救」在她即将攻击时、身边有生命不高于一半的队友才发动,这次攻击直接改为治疗;新约能天使二技能的护盾按官方给攻击范围内**仇恨最高**的队友(嘲讽等级高的优先,同级时给最后部署的),不一定是站在最前面挨打的那个。手动技能的自动释放有 3 秒冷却:开战部署后先等 3 秒,之后同一干员两次自动释放至少间隔 3 秒(可充能的技能不会同时放完)。 - **推开 / 拉拽**按官方「力度 − 重量」决定距离:例如野鬃二技能(中等力度)把重量 0 / 1 / 2 / 3 的敌人推开约 2.1 / 1.7 / 0.4 / 0.1 格,重量 4 以上推不动——轻型敌人反而推得更远。野鬃二技能沿她的朝向推开:正前方 45° 以内按上面的距离,身后、侧面或贴身的敌人只被轻推一下(约 0.4 格以内);琳琅诗怀雅三技能的金币则从她身边向外推开,落在她前方 2–4 格的范围(官方「范围2-4」)内的地面敌人和她阻挡的敌人身上。攻击速度最低为 20(沼泽、寒霜等减速叠满时也不会更慢)。 - **元素损伤条**在单位自己的血条正下方:左边是元素图标,白条是剩余的元素值,只有真正爆发时才会耗尽;爆发后条变成元素的颜色、图标闪烁,在冷却期间慢慢回满(这段时间不会再积累元素损伤)。 - **敌人**:远程敌人的射程按官方从干员的碰撞体积(半径 0.25 格)算起,比数据上的攻击范围半径远 0.25 格——例如萨卡兹枯朽战车(射程 2.2)能打到中心 2.45 格以内、站在低地的干员。没被阻挡的远程敌人每次攻击都会站定、播完攻击动作,两次攻击之间才继续前进。它的第一下攻击就是「秽蚀轰击」(在目标处留下半径 1.7、持续 10 秒的污染区:低地每秒 50、高台 25 点真实伤害,同名不叠加),被阻挡时普通攻击伤害翻倍。“萨科塔之翼”等血量第一次低于一半时会在原地小范围乱飞 5 秒(官方叫「恐惧」);叙拉古、妮芙等干员造成的恐惧会让敌人朝远离干员的方向逃跑。隐匿的敌人只有被阻挡(或被揭示)时才能被选为攻击目标,溅射、技能的圆形范围伤害也打不到没被挡住的隐匿敌人(和官方一样);阻挡解除后 3 秒内它仍然可以被选中(官方页面注明 0 秒或 1 秒的敌人按页面),之后才重新隐匿:深池逐火战士每次被击倒后先重生 1 秒,然后变成继续往前走的隐匿「余烬」(需要 5 次伤害击倒,护卫的「火灰」10 次),一般要用干员挡住它才能选中(挡住时它不再半透明;被阻挡时被击倒的,重生结束后约 2 秒内也能用远程或范围伤害补刀),10 秒内没打掉就满血站起来;敌人重生时身上的减速、恐惧等状态会被清空、技能冷却从头开始,被恐惧的逐火战士变成余烬后也能被挡住;转译基底·α 原始形态受到的伤害全部无效,受到第 4 次物理伤害、第 4 次法术伤害或被阻挡时花 2 秒变成寻仇者、特战术师或幽灵,之后才能被击倒。 diff --git a/docs/SIM.md b/docs/SIM.md index 1a74353c..cf49bb5f 100644 --- a/docs/SIM.md +++ b/docs/SIM.md @@ -128,7 +128,10 @@ device or 海嗣 that was not placed never appears (the hidden 待部署区 depl 流形 still come as their 援军 on a tactical point without a piece (content/tokens.js `tacticalPoint`). Their piece stands inside the tactician's attack range — the prep enforces "只能部署在召唤者攻击范围内" (tokens.json `ownerRange`, player report #9 after 0.1.0) — and the kits re-use the piece's tile for a re-summoned pack only while it is a free, standable -tile of her initial range (kits/shared/tier3.js `tacticalPoint`, tokens.js `ensureReinforcement`). The start deploy +tile of her initial range (kits/shared/tier3.js `tacticalPoint`, tokens.js `ensureReinforcement`). A 狼群 whose last 狼影 +falls (or that is retreated) is not re-summoned: it stays its tile's piece in its 战术点形态 — off the fight, the tile +reserved — for the 狼影 interval and comes back there with one 狼影 (tokens.js `installWolfTacticalPoint`, the piece and +the 伺夜 kit's own pack alike; 伺夜 S1 ① ends the form at once, 伺夜 leaving ends it without a return). The start deploy is the user's call after playtest #6 (DESIGN §20); `shared/constants.js SKILL_SUMMON_START_DEPLOY = false` would bring back the playtest #4 reading (only with the skill) in the sim and the summon card's hint (docs/PLAYING.md §4 and this passage must follow; test/ui/playtest6_summons.test.js checks). diff --git a/server/sim/content/kits/ops/chess_char_3_19-vigil.js b/server/sim/content/kits/ops/chess_char_3_19-vigil.js index a81e6422..abaede8b 100644 --- a/server/sim/content/kits/ops/chess_char_3_19-vigil.js +++ b/server/sim/content/kits/ops/chess_char_3_19-vigil.js @@ -2,18 +2,23 @@ // Conventions of the tier-3 kits: ../shared/tier3.js; kit contract and rules: ../README.md. import { num, defOf, talentBb, selectedId, altSkills, alive, fx, textNum, tacticalPoint } from '../shared/tier3.js'; +import { installWolfTacticalPoint, wolfReturnNow, wolfShadowInterval, wolfTacticalPoint } from '../../tokens.js'; /** Tokens `tokenId` summoned by / placed for `owner` (board pieces included). */ const tokensOf = (battle, owner, tokenId) => battle.allyUnits.filter((t) => t.kind === 'token' && t.defId === tokenId && t.ownerUnit === owner && !t.mem.isClone); export default { // ---- 3_19 伺夜 · 战术家 — the tactical reinforcement is the wolf pack (狼群领袖: 2 wolves, +1 every 25 s up to 3, each - // wolf = +1 block and one more bite, a wolf is lost instead of the pack dying); 狼群天性: DEF ignore vs pack-blocked + // wolf = +1 block and one more bite, a wolf is lost instead of the pack dying; the fatal hit on the last one, or a + // 撤退, puts the pack in its 战术点形态 — out of the fight for the 狼影 interval, then back on its tile with one + // wolf: tokens.js installWolfTacticalPoint, PRTS 狼群领袖 备注); 狼群天性: DEF ignore vs pack-blocked // enemies; S3 领袖的尊严: DP over time, 三连击, bonus arts vs pack-blocked enemies; 精锐 module: pack takes less // damage from the enemies it blocks (token module talent). A 狼群 piece placed in the prep phase is the pack. - // S1 领袖的呼唤 (自动触发, only while the pack is on the field): +cost DP and one more “狼影” (≤ the talent's - // maximum); S2 领袖的馈赠: +cost DP, the pack recovers hp_ratio of its max HP and its next attack hits ×atk_scale — - // a kill by that attack gives +cost DP. + // S1 领袖的呼唤 (自动触发, only while the pack is on the field — standing or in its 战术点形态): +cost DP, and by the + // pack's state (PRTS 备注) ① 战术点形态: back at once with one “狼影” and a fresh growth cycle, ② below the + // talent's maximum: one more “狼影” (its own cycle untouched), ③ at the maximum: the pack's HP to max; S2 领袖的馈赠 + // (only while the pack stands): +cost DP, the pack recovers hp_ratio of its max HP and its next attack hits + // ×atk_scale — a kill by that attack gives +cost DP. // 精锐 module TAC-Y: trait ×165 % (profession layer) and "援军阻挡的敌人更容易受到我方的攻击": the pack's token module // talent taunt_level (+1) goes to the enemies it blocks — the enemy-side 嘲讽等级 our operators target first // (targeting.js; research 05: "更容易受到我方的攻击" = enemy taunt.taunt_level), never to the pack itself. @@ -27,7 +32,13 @@ export default { const hits = textNum(d.skill?.description, /(\d+|[一二两三四五])连击/, 3); const bonus = num(bb['attack@vigil_s_3.atk_scale'], 0); const pen = num(t1.def_penetrate_fixed, 0); + /** The standing pack (狼群天性, S3, the TAC-Y mark, S2). */ const wolfOf = (u) => (u.trait.reinforcement && u.trait.reinforcement.alive ? u.trait.reinforcement : null); + /** The pack on the field in either form — standing, or in its 战术点形态 (only while 伺夜 stands: tokens.js). */ + const packOf = (u) => { + const w = u.trait.reinforcement; + return w && (w.alive || wolfTacticalPoint(w)) ? w : null; + }; const wolfKit = (vigil) => ({ skill: null, trait: { hitsFn: (b, w) => Math.max(1, w.mem.wolves || 1) }, @@ -35,11 +46,10 @@ export default { const tal = w.def.talents || []; const wb = tal[0]?.bb ?? {}; const per = num(wb.block_cnt, num(wb['vigil_wolf_t_1_enhance[trigger].block_cnt'], 1)); - const grow = num(wb.interval, num(wb['vigil_wolf_t_1_enhance[trigger].interval'], 25)); + // the 狼影 interval from the data (tokens.js wolfShadowInterval): the growth cycle — and the 战术点形态's length + const grow = wolfShadowInterval(w.def); // (mem.shadows mirrors the count for content reading tokens.js wolfShadows()) const apply = () => { w.mem.shadows = w.mem.wolves; battle.addBuff(w, { key: 'token:wolves', mods: { blockCnt: per * w.mem.wolves }, allowDead: true }); }; - w.mem.wolves = Math.max(1, Math.min(maxWolves, initial)); - apply(); w.mem.addWolf = () => { if (!w.alive || w.mem.wolves >= maxWolves) return false; w.mem.wolves++; @@ -47,7 +57,23 @@ export default { fx(battle, 'summon', w, { src: vigil.id, wolves: w.mem.wolves }); return true; }; - battle.every(grow, () => w.mem.addWolf(), { owner: w }); + // every deployment: the initial count — or one “狼影” back from the 战术点形态 (tokens.js wolfReturnNow: "持续时间 + // 结束后狼影层数变回1层", S1 ①) — and a fresh growth cycle (the previous one stops at its next tick) + battle.on('deploy', (ctx) => { + if (ctx.unit !== w) return; + const back = w.mem.wolfReturn; + w.mem.wolves = back ? 1 : Math.max(1, Math.min(maxWolves, initial)); + apply(); + if (back && back.src == null) fx(battle, 'summon', w, { wolves: w.mem.wolves }); // (S1 ① shows its own) + const seq = w.deploySeq; + if (grow > 0) { + battle.every(grow, (b, sched) => { + if (!w.alive || w.deploySeq !== seq) { sched.cancel(); return; } + w.mem.addWolf(); + }, { owner: w }); + } + }, { owner: w, priority: 20 }); + // fatal with more than one wolf: one is lost, full HP; on the last one the knock-out goes through — 战术点形态 battle.on('fatal', (ctx) => { if (ctx.unit !== w || ctx.prevented || w.mem.wolves <= 1) return; w.mem.wolves--; @@ -56,6 +82,13 @@ export default { w.hp = w.s.maxHp; fx(battle, 'revive', w, { wolves: w.mem.wolves }); }, { owner: w }); + installWolfTacticalPoint(battle, w, { + onEnter: () => { + w.mem.wolves = 0; + apply(); + fx(battle, 'wolfShadowLost', w, { wolves: 0 }); + }, + }); const mod = tal.find((t) => t && t.bb && t.bb.damage_scale != null); if (mod) { battle.on('hit', (ctx) => { @@ -102,7 +135,10 @@ export default { cur = next; }, { owner: unit, immediate: true }); }; - /** One more “狼影” (S1): this kit's pack, or a 狼群 board piece run by content/tokens.js (same 'wolf:shadows' buff). */ + /** + * One more “狼影” (S1 ②) on a standing pack: this kit's pack, or a 狼群 board piece run by content/tokens.js (same + * 'wolf:shadows' buff). False at the maximum. Neither pack's own growth cycle is touched ("与狼群本身的刷新周期互相独立"). + */ const addShadow = (battle, vigil, w) => { if (typeof w.mem.addWolf === 'function') return w.mem.addWolf(); const n = Math.max(1, w.mem.shadows ?? 1); @@ -115,17 +151,20 @@ export default { return true; }; const dpGain = (battle, unit, n) => { if (n > 0) { battle.addDp(unit.ownerId, n); fx(battle, 'dp', unit, { n }); } }; - // S1: the cast — ready and the pack on the field (PRTS 备注 「仅场上存在狼群时可触发技能」) + // S1: the cast — ready and the pack on the field in either form (PRTS 备注 「仅场上存在狼群时可触发技能」 with ① for its + // 战术点形态) const installCall = (battle, unit) => { battle.on('tick', () => { const sk = unit.skill; - if (!alive(unit) || !sk || !sk.ready || sk.active || !unit.canAct || unit.s.flags.silence || !wolfOf(unit)) return; + if (!alive(unit) || !sk || !sk.ready || sk.active || !unit.canAct || unit.s.flags.silence || !packOf(unit)) return; sk.activate('SP_FULL'); }, { owner: unit }); }; // S2: the pack's empowered next attack (armed by the cast; ×scale on its hits; a kill pays once) const installGift = (battle, unit) => { - battle.on('tick', () => { // the cast: ready, the pack on the field, no unused gift on it (PRTS 备注) + // the cast: ready, the pack standing, no unused gift on it (PRTS 备注 「仅场上存在狼群,且狼群未获得此技能的充能时可触发 + // 技能」; a pack in its 战术点形态 does not count — the 备注 does not say [ASSUMED]) + battle.on('tick', () => { const sk = unit.skill, w = wolfOf(unit); if (!alive(unit) || !sk || !sk.ready || sk.active || !unit.canAct || unit.s.flags.silence || !w || w.mem.vigilGift) return; sk.activate('SP_FULL'); @@ -152,16 +191,19 @@ export default { trait: { install(battle, unit) { // The pack is the tactician's 援军. The match also hands the player the 狼群 token to place in the prep phase // (= choosing the tactical point): that board piece (tokens.js kit, owner-coupled effects left to this kit) is - // the pack when present — deployed early on its own tile if 伺夜 deploys first — never a second pack. + // the pack when present — deployed early on its own tile if 伺夜 deploys first — never a second pack. Summoned at + // each deployment of 伺夜; a knocked-out pack is not summoned again: it sits in its 战术点形态 and comes back by + // itself (tokens.js installWolfTacticalPoint — both packs; until 0.2.0 it came back after the token's 10 s redeploy + // time with its initial wolves). const spawn = () => { - if (!alive(unit) || wolfOf(unit)) return; + if (!alive(unit) || packOf(unit)) return; const pieces = tokensOf(battle, unit, wolfId); const live = pieces.find((t) => alive(t)); if (live) { unit.trait.reinforcement = live; return; } const waiting = pieces.find((t) => !t.alive && !t.removed); if (waiting && battle.redeploy(waiting, { free: true })) { unit.trait.reinforcement = waiting; - fx(battle, 'summon', waiting, { src: unit.id, token: wolfId }); + fx(battle, 'summon', waiting, { src: unit.id, token: wolfId, wolves: waiting.mem.shadows }); return; } const board = pieces.find((t) => t.uid != null); @@ -171,11 +213,11 @@ export default { unit.trait.reinforcement = w; if (!w) return; fx(battle, 'summon', w, { src: unit.id, token: wolfId, wolves: w.mem.wolves }); - const again = Math.max(0, w.base.respawnTime || 0); - battle.on('death', (c) => { if (c.unit === w && c.reason === 'killed') battle.after(again, spawn, { owner: unit }); }, { owner: w }); }; battle.on('deploy', (c) => { if (c.unit === unit) spawn(); }, { owner: unit }); installPackMark(battle, unit); + // "持有者离场后强制撤退场上的狼群(不触发上述效果)": a standing pack leaves with him ('expired': no 战术点形态); one in + // its 战术点形态 ends it there (tokens.js) and waits for his redeploy battle.on('death', (c) => { if (c.unit !== unit) return; const w = wolfOf(unit); @@ -205,23 +247,34 @@ export default { }, skills: altSkills(chess, d, bb, { // (自动触发: an AUTO skill takes no 技能策略 — the 战术家 row is for MANUAL skills. PRTS 备注 「仅场上存在狼群时可触发技能」 - // (the owner's decision of 2026-10-05): the kit casts it as soon as it is ready while the pack stands - // (installCall); without a pack it waits at full SP — until 0.2.0 it fired at SP_FULL and paid its DP with no pack. - // The 备注 also counts the pack's 战术点形态 (after its last 狼影 is lost) as on the field — the cast brings it back - // at once with one 狼影 — and at the 狼影 cap resets the pack's HP; the kit has neither: a knocked-out pack comes - // back after its respawn time and S1 waits for it, and at the cap it only pays its DP [ASSUMED]) + // (the owner's decision of 2026-10-05): the kit casts it as soon as it is ready while the pack is on the field — + // standing, or in its 战术点形态 while 伺夜 stands (installCall, packOf); without a pack it waits at full SP — until + // 0.2.0 it fired at SP_FULL and paid its DP with no pack. The cast always pays +cost DP; its effect follows the + // pack's state (备注 ①②③): ① in its 战术点形态 the pack comes back at once with one 狼影 and a fresh 狼影 cycle (its + // pending return is cancelled — tokens.js wolfReturnNow), ② standing below the maximum: one more 狼影 (the pack's + // own cycle untouched), ③ at the maximum: the pack's HP to max — a reset like the talent's, not a heal (the pack + // holds 禁疗). Until 0.2.0 a knocked-out pack was waited for and the cast at the maximum only paid its DP.) skchr_vigil_1: (s) => ({ kind: 'instant', trigger: 'NEVER', onStart({ battle, unit }) { dpGain(battle, unit, num(s.bb.cost, 0)); - const w = wolfOf(unit); - if (w) addShadow(battle, unit, w); + const w = packOf(unit); + if (!w) return; + if (!w.alive) { // ① + if (wolfReturnNow(battle, w, unit.id)) fx(battle, 'summon', w, { src: unit.id, wolves: w.mem.shadows }); + return; + } + if (addShadow(battle, unit, w)) return; // ② + w.hp = w.s.maxHp; // ③ + fx(battle, 'revive', w, { src: unit.id, wolves: w.mem.shadows }); }, }), // (自动触发, PRTS 备注 「仅场上存在狼群,且狼群未获得此技能的充能时可触发技能」: no enemy needed — the kit casts it as soon // as it is ready while the pack stands and holds no unused gift (installGift); until 0.2.0 the data's DEFAULT made it - // wait for 伺夜's own next attack, so it never fired with no enemy in his range) + // wait for 伺夜's own next attack, so it never fired with no enemy in his range. A pack in its 战术点形态 does not + // count: S1's 备注 lists that form, S2's says nothing of it, so S2 waits for the pack to come back [ASSUMED]; a gift + // the pack holds stays on it through the form [ASSUMED]) skchr_vigil_2: (s) => ({ kind: 'instant', trigger: 'NEVER', diff --git a/server/sim/content/tokens.js b/server/sim/content/tokens.js index 047eca6b..2efd6323 100644 --- a/server/sim/content/tokens.js +++ b/server/sim/content/tokens.js @@ -21,7 +21,8 @@ // full HP; its and 伺夜's attacks ignore def_penetrate_fixed DEF of enemies it blocks; while 伺夜's // timed skill runs every attack damage instance (each bite) of the pack / 伺夜 on an enemy it blocks // adds 伺夜 ATK × bb scale arts; module: ×damage_scale damage from enemies it blocks; is its owner's -// tactician 援军 (1.5× trait), leaves with it; respawns respawnTime s after being killed +// tactician 援军 (1.5× trait), leaves with it; the fatal hit on its last shadow (or a 撤退) ⇒ 战术点形态 +// for the talent interval, then back on its tile with 1 shadow (installWolfTacticalPoint, both packs) // 流形 copy skill (SP from data, golden +sp; starts only when an operator can be copied, else waits ready): // copies scale × HP/ATK/DEF/RES, block, BAT/ASPD, range and the damage type of the nearest allied // operator; no attack before its copy; ranged copy splits every N attacks (clone lasts the talent @@ -74,10 +75,12 @@ // makes the token (Battle.spawnToken also refuses summons the owner's loadout does not produce). // // Exports for other content: spawnYanyou, spawnMapChar, findSummonTile, summonToken, tacticalPoint, wolfShadows, -// releaseSkillSummon, SKILL_SUMMON_START_DEPLOY, CAT_SHIELD_KEY, TOKEN_IDS; mapCharTalents and touchGospel (Touch's 攫升 / -// 超脱 and 恳切福音, shared with the Touch 补位 stand-in kit, kits/ops/standin-acmedc.js). +// wolfShadowInterval, installWolfTacticalPoint, wolfTacticalPoint, wolfReturnNow (the 狼群's 战术点形态, shared with the +// 伺夜 kit's own pack, kits/ops/chess_char_3_19-vigil.js), releaseSkillSummon, SKILL_SUMMON_START_DEPLOY, CAT_SHIELD_KEY, +// TOKEN_IDS; mapCharTalents and touchGospel (Touch's 攫升 / 超脱 and 恳切福音, shared with the Touch 补位 stand-in kit, +// kits/ops/standin-acmedc.js). -import { COLS, ROWS, MOVE_SCALE } from '../constants.js'; +import { COLS, ROWS, MOVE_SCALE, FORCED_EXIT } from '../constants.js'; import { absoluteRangeKeys, sortEnemyTargets, canTargetEnemy } from '../targeting.js'; import { bodyInKeys, bodyOnTile } from '../body.js'; import { hasHp } from '../damage.js'; @@ -607,14 +610,109 @@ function paperDoll(bb, raw, def) { }; } -/** Current “狼影” count of a 狼群 token (0 when not a wolf pack). */ +/** Current “狼影” count of a 狼群 token (0 when not a wolf pack, and in its 战术点形态). */ export function wolfShadows(unit) { return unit && unit.defId === TOKEN_IDS.wolfPack ? (unit.mem.shadows ?? 0) : 0; } +// ---- 狼群 战术点形态 — both packs: the board piece (wolfPack below) and the 伺夜 kit's own pack +// (kits/ops/chess_char_3_19-vigil.js). PRTS 伺夜 天赋 狼群领袖 备注 and 狼群 召唤物信息 备注: "受到致命伤时,如果狼影层数>1则 +// 消耗一层狼影并重设生命值至上限,为1则消耗一层狼影变为战术点形态;手动撤退、强制撤退时狼影层数归零并变为战术点形态;战术点形态的 +// 持续时间等于“狼影”恢复时间,持续时间结束后狼影层数变回1层", "战术点形态期间:不进行普通攻击,持有无敌、强制缴械、不死…", +// "持有者离场后强制撤退场上的狼群(不触发上述效果)". The remake's 战术点形态 is its knocked-out piece: off the fight +// (`alive` false: no block, no attack, not targetable) but kept (`removed` false: its hooks live on and its tile stays +// reserved — Battle.isReservedTile — as the official device holds it), with 0 狼影, for the 狼影 recovery time +// (wolfShadowInterval: the talent's interval, data); then it is redeployed on its tile, free, at full HP with 1 狼影 and a +// fresh growth cycle (the pack's own `deploy` handler reads `mem.wolfReturn`). 伺夜 S1 领袖的呼唤 ① brings it back at +// once (wolfReturnNow); every return timer carries the form's `seq`, so a stale one never revives a pack that came back +// and fell again. + +/** + * Removal reasons that put a 狼群 in its 战术点形态: the fatal hit on its last 狼影 (the engine's knock-out) and a 撤退 — + * manual (`battle.retreat`'s default reason) or forced. No sim path retreats the pack that way today; 'expired' (its + * owner leaving, the deploy limit) and 'raid' (an instant redeploy) never do. + */ +const WOLF_TAC_EXITS = new Set(['killed', 'retreat', FORCED_EXIT]); +/** Seconds between two return tries of a 狼群 whose tile is taken when its 战术点形态 ends (Battle.isReservedTile keeps it). */ +const WOLF_RETURN_RETRY = 0.25; + +/** The 狼群领袖 talent of a 狼群 def (its 狼影 stack talent), else its first talent. */ +const wolfLeader = (def) => talentWith(def, 'vigil_wolf_t_1_enhance[trigger].max_stack_cnt') ?? def?.talents?.[0] ?? null; + +/** + * “狼影” recovery time of a 狼群 def: its 狼群领袖 talent interval ("每25秒增加一只"; data — 25 s for both 伺夜 chess). The + * growth cycle and the length of the 战术点形态. 0 when the data has none. + */ +export function wolfShadowInterval(def) { + const lb = wolfLeader(def)?.bb ?? {}; + return Math.max(0, num(lb['vigil_wolf_t_1_enhance[trigger].interval'] ?? lb.interval, 0)); +} + +/** The 战术点形态 of a 狼群 (`{ seq, since, until }`) while it is in it, else null. */ +export function wolfTacticalPoint(unit) { + const tp = unit?.mem?.wolfTac; + return tp && !unit.alive && !unit.removed ? tp : null; +} + +/** The pack's owner stands on the field (no owner unit: a test spawn). */ +const wolfOwnerStands = (u) => !u.ownerUnit || (u.ownerUnit.alive && u.ownerUnit.deployed); + +/** + * Bring a 狼群 back from its 战术点形态 at once (the end of the form; 伺夜 S1 ①: "立刻切换至拥有1只狼影的召唤物形态(会更新 + * 狼群的狼影刷新周期)"): redeployed on its tile, free, at full HP with 1 狼影 and a fresh growth cycle (its `deploy` + * handler reads `mem.wolfReturn` = { src }); the pending return is cancelled. False when it is not in the form, its owner + * is off the field or its tile is taken. + */ +export function wolfReturnNow(battle, unit, src = null) { + const tp = wolfTacticalPoint(unit); + if (!tp || battle.finished || !wolfOwnerStands(unit)) return false; + unit.mem.wolfReturn = { src }; + let ok; + try { ok = battle.redeploy(unit, { free: true }); } finally { unit.mem.wolfReturn = null; } + if (!ok) return false; + if (unit.mem.wolfTac === tp) unit.mem.wolfTac = null; + if (tp.timer) tp.timer.cancel(); + return true; +} + +/** The end of the 战术点形态 `seq` (its timer): the pack comes back — another try shortly when its tile is taken. */ +function wolfTimerReturn(battle, unit, seq) { + const tp = wolfTacticalPoint(unit); + if (!tp || tp.seq !== seq || battle.finished) return; + if (wolfReturnNow(battle, unit)) return; + tp.timer = battle.after(WOLF_RETURN_RETRY, () => wolfTimerReturn(battle, unit, seq), { owner: unit }); +} + +/** + * The 战术点形态 of a 狼群 unit (see above). `onEnter(battle, unit)` sets its 狼影 to 0 — each pack keeps its own count, + * buff and fx. + */ +export function installWolfTacticalPoint(battle, unit, { onEnter = null } = {}) { + battle.on('death', (ctx) => { + if (ctx.unit !== unit || battle.finished || !WOLF_TAC_EXITS.has(ctx.reason) || !wolfOwnerStands(unit)) return; + unit.removed = false; + // the 狼影 recovery time from the data; a record without one falls back to the token's redeploy time (data) + const t = wolfShadowInterval(unit.def) || Math.max(0, num(unit.base.respawnTime, 0)); + const seq = (unit.mem.wolfTacSeq ?? 0) + 1; + unit.mem.wolfTacSeq = seq; + const tp = { seq, since: battle.time, until: battle.time + t, timer: null }; + unit.mem.wolfTac = tp; + if (onEnter) onEnter(battle, unit); + tp.timer = battle.after(t, () => wolfTimerReturn(battle, unit, seq), { owner: unit }); + }, { owner: unit, priority: -10 }); + // its owner leaving ends the form without a return ("不触发上述效果"): the piece waits for the owner's redeploy, which + // brings a fresh pack (its initial 狼影) — as for a standing pack, which leaves with its owner + battle.on('death', (ctx) => { + const tp = wolfTacticalPoint(unit); + if (!tp || !unit.ownerUnit || ctx.unit !== unit.ownerUnit) return; + unit.mem.wolfTac = null; + if (tp.timer) tp.timer.cancel(); + }, { owner: unit }); +} + /** 狼群 (伺夜 talent 狼群领袖/狼群天性; S3 bb on the token). */ function wolfPack(bb, raw, def) { - const leader = talentWith(def, 'vigil_wolf_t_1_enhance[trigger].max_stack_cnt') ?? def?.talents?.[0] ?? null; + const leader = wolfLeader(def); const lb = leader?.bb ?? {}; - const interval = num(lb['vigil_wolf_t_1_enhance[trigger].interval'] ?? lb.interval, 0); + const interval = wolfShadowInterval(def); const perBlock = num(lb['vigil_wolf_t_1_enhance[trigger].block_cnt'] ?? lb.block_cnt, 1); const mMax = String(leader?.description ?? '').match(/至多(\d+)只/); const maxShadows = mMax ? +mMax[1] : 1 + num(lb['vigil_wolf_t_1_enhance[trigger].max_stack_cnt'], 0); @@ -635,8 +733,13 @@ function wolfPack(bb, raw, def) { const mi = ot.match(/初始(两|二|\d+)只/); const initShadows = Math.max(1, Math.min(maxShadows, mi ? (/\d/.test(mi[1]) ? +mi[1] : 2) : maxShadows - 1)); onDeploy(battle, unit, () => { - setShadows(battle, unit, initShadows); + // back from its 战术点形态 (wolfReturnNow): 1 狼影 (PRTS 狼群 "从战术点形态转变为召唤物形态后拥有1只“狼影”") and a + // fresh growth cycle; any other deployment: the initial count + const back = unit.mem.wolfReturn; + setShadows(battle, unit, back ? 1 : initShadows); unit.hp = unit.s.maxHp; + // (伺夜 S1 ① shows its own summon fx) + if (back && back.src == null) battle.fx('wolfShadow', { x: unit.x, y: unit.y, id: unit.id, n: 1 }); const seq = unit.deploySeq; if (interval > 0) { battle.every(interval, (b, sched) => { @@ -650,6 +753,7 @@ function wolfPack(bb, raw, def) { const o = ownerOf(unit); if (o && o.profile?.sub === 'tactician') o.trait.reinforcement = unit; }, 20); + // fatal with more than one 狼影: one is lost, full HP; on the last one the knock-out goes through — 战术点形态 battle.on('fatal', (ctx) => { if (ctx.unit !== unit || ctx.prevented || !((unit.mem.shadows ?? 0) > 1)) return; ctx.prevented = true; @@ -657,6 +761,12 @@ function wolfPack(bb, raw, def) { unit.hp = unit.s.maxHp; battle.fx('wolfShadowLost', { x: unit.x, y: unit.y, id: unit.id, n: unit.mem.shadows }); }, { owner: unit, priority: -50 }); + installWolfTacticalPoint(battle, unit, { + onEnter: (b, u) => { + setShadows(b, u, 0); + b.fx('wolfShadowLost', { x: u.x, y: u.y, id: u.id, n: 0 }); + }, + }); // 狼群天性 ("伺夜和狼群对其的攻击无视其175防御力") and the owner's S3 bonus ("狼群与伺夜攻击被狼群阻挡的单位造成伤害 // 时,额外造成相当于伺夜攻击力N%的法术伤害", one per damage instance = per bite) cover the pack's and 伺夜's own // attacks; with a hand-authored 伺夜 kit (managed) that kit applies both. The module guard is intrinsic. @@ -676,14 +786,14 @@ function wolfPack(bb, raw, def) { battle.dealDamage(ctx.source, e, { amount: o.s.atk * s3Scale, type: 'arts', isSkill: true, canDodge: false, tags: ['vigil'] }); }, { owner: unit }); } - // the 援军 leaves with its tactician (a hand-authored 伺夜 kit does the same for its pack) and a killed pack - // does not come back while its tactician is down (the tactician's redeploy brings it back) + // the 援军 leaves with its tactician (a hand-authored 伺夜 kit does the same for its pack); a pack in its 战术点形态 + // ends it then (installWolfTacticalPoint) and the tactician's redeploy brings it back fresh. No respawn timer of + // its own: the token's redeploy time (data 10 s) is not the 战术点形态's length (the 狼影 interval) const tacticianOwner = () => ownerOf(unit)?.profile?.sub === 'tactician'; battle.on('death', (ctx) => { if (ctx.unit !== unit.ownerUnit || !unit.alive || managed(unit) || !tacticianOwner()) return; battle.retreat(unit, { reason: 'expired', permanent: true }); }, { owner: unit }); - enableRespawn(battle, unit, { delay: (u) => u.base.respawnTime, requireOwner: tacticianOwner }); }, }; } @@ -1464,6 +1574,10 @@ function ensureReinforcement(battle, owner, tokenId) { const mine = battle.allyUnits.filter((t) => t.kind === 'token' && t.defId === tokenId && t.ownerUnit === owner && !t.mem.isClone && !t.mem.mlyssClone); const live = mine.find((t) => t.alive); if (live) { owner.trait.reinforcement = live; return live; } + // a 狼群 in its 战术点形态 is still the 援军 on the field: it comes back by its own timer (a 【移动】 of the owner fires + // `deploy` without ending the form) + const tac = mine.find((t) => wolfTacticalPoint(t)); + if (tac) { owner.trait.reinforcement = tac; return tac; } const waiting = mine.find((t) => !t.alive && !t.removed); if (waiting && battle.redeploy(waiting, { free: true })) { owner.trait.reinforcement = waiting; return waiting; } // the tactical point the player chose (the board piece's tile, else the last one's) when still usable, else the diff --git a/test/content/kits_alt_t3.test.js b/test/content/kits_alt_t3.test.js index 4a9e767c..7769d7d8 100644 --- a/test/content/kits_alt_t3.test.js +++ b/test/content/kits_alt_t3.test.js @@ -8,6 +8,7 @@ import { getDefaultSource } from '../../server/sim/simdata.js'; import { skillSpecSource } from '../../server/sim/content/index.js'; import { effectiveProfile } from '../../server/sim/ai.js'; import { kitCoverage } from '../../tools/kit-coverage.mjs'; +import { wolfShadows, wolfTacticalPoint } from '../../server/sim/content/tokens.js'; import KITS from '../../server/sim/content/kits/tier3.js'; const ds = getDefaultSource(); @@ -786,28 +787,186 @@ test('3_19 伺夜 S1 领袖的呼唤 (自动触发, the pack on the field): +DP done(g); }); -test('3_19 伺夜 S1 领袖的呼唤: PRTS 备注 「仅场上存在狼群时可触发技能」 — no pack, no cast (the SP waits full, no DP); cast once the pack stands again', () => { +test('3_19 伺夜 S1 领袖的呼唤: PRTS 备注 「仅场上存在狼群时可触发技能」 — no pack on the field in either form, no cast (the SP waits full, no DP)', () => { for (const id of BOTH('chess_char_3_19_a')) { - const b = SB(id, 'skchr_vigil_1'); const h = makeBattle({ defs: { chess: noGarrison(id) }, timeLimit: 60, flags: { dpPerSec: 0 }, units: [U(id, 'skchr_vigil_1', 10, 3)] }); const u = h.unit(id), p = h.b.getPlayer('p1'); h.run(1); const w = u.trait.reinforcement; assert.ok(w && w.alive, 'the pack stands'); - for (let i = 0; i < 10 && w.alive; i++) h.b.dealDamage(null, w, { amount: 1e9, type: 'true' }); // a wolf is lost per KO - assert.ok(!u.trait.reinforcement?.alive, 'no pack on the field'); + // withdrawn by a rule ('expired', as when it leaves with 伺夜): gone, not in its 战术点形态 + h.b.retreat(w, { reason: 'expired', permanent: true }); + assert.equal(w.removed, true); + assert.equal(wolfTacticalPoint(w), null, 'no 战术点形态'); const dp0 = p.dp; fill(u); h.run(2); assert.equal(u.skill.activations, 0, 'no pack, no cast'); assert.ok(u.skill.ready, 'the SP waits full'); approx(p.dp, dp0, 1e-9, 'no DP without the pack'); - // the knocked-out pack comes back after its respawn time: cast at once — +DP and one more 狼影 - assert.ok(h.runUntil(() => u.skill.activations === 1, 15), 'cast once the pack stands again'); + done(h); + } +}); + +// 狼群 战术点形态 (PRTS 伺夜 天赋 狼群领袖 备注, 狼群 召唤物信息 备注) — both packs: the 伺夜 kit's own pack (no board piece) and +// the 狼群 piece placed in the prep phase (content/tokens.js wolfPack, adopted by the kit) +const WOLF = 'token_10028_vigil_wolf'; +/** The 狼影 recovery time from the data: the token's 狼群领袖 talent interval. */ +const WOLF_IV = (id) => ds.getToken(WOLF, id).talents.find((t) => t.bb['vigil_wolf_t_1_enhance[trigger].interval'] != null).bb['vigil_wolf_t_1_enhance[trigger].interval']; +/** 伺夜 `id` (skill `skillId`, default S3 when null) with the kit's pack, or with a prep-placed 狼群 piece (uid 2). */ +const vigilUnits = (id, skillId, piece) => { + const sk = skillId ? { skillIndex: IDX(id, skillId) } : {}; + return piece + ? [{ chessId: id, row: 12, col: 3, uid: 1, ...sk }, { kind: 'token', tokenId: WOLF, ownerUid: 1, row: 11, col: 5, uid: 2 }] + : [{ chessId: id, row: 10, col: 3, uid: 1, ...sk }]; +}; +const close = (a, b, tol, msg) => assert.ok(Math.abs(a - b) <= tol, `${msg}: ${a} ≈ ${b}`); +const silence = (h, u, on) => (on ? h.b.addBuff(u, { key: 'test:silence', flags: { silence: true } }) : h.b.removeBuff(u, 'test:silence')); + +test('3_19 伺夜 狼群 战术点形态: the fatal hit on the last 狼影 — out of the fight for the 狼影 interval (data), then back on its tile at full HP with one 狼影 and a fresh cycle (kit pack and prep piece)', () => { + for (const id of BOTH('chess_char_3_19_a')) for (const piece of [false, true]) { + const iv = WOLF_IV(id), tag = `${id} ${piece ? 'piece' : 'kit pack'}`; + assert.equal(iv, 25, '25 s on both chess (data)'); + const h = makeBattle({ defs: { enemies: { enemy_d: dummy('enemy_d', { atk: 0 }) }, chess: noGarrison(id) }, timeLimit: 120, hooks: ['attack'], flags: { dpPerSec: 0 }, + units: vigilUnits(id, null, piece), enemies: [{ key: 'enemy_d', pos: piece ? [11, 5] : [9, 3], time: 0.5 }] }); + h.run(2); + const u = h.unit(1), w = u.trait.reinforcement, e = h.enemy('enemy_d'); + if (piece) assert.equal(w, h.unit(2), `${tag}: the piece is the pack`); + else assert.ok(w && w.defId === WOLF && w.uid == null, `${tag}: the kit's own pack`); + assert.equal(e.blockedBy, w, `${tag}: blocks`); + const tile = [w.tileR, w.tileC]; + h.b.dealDamage(null, w, { amount: 1e9, type: 'true' }); + assert.ok(w.alive && wolfShadows(w) === 1, `${tag}: one 狼影 lost, still standing`); + h.b.dealDamage(null, w, { amount: 1e9, type: 'true' }); + const t0 = h.b.time; + assert.equal(w.alive, false, `${tag}: out of the fight`); + assert.ok(wolfTacticalPoint(w), `${tag}: 战术点形态`); + assert.equal(w.removed, false, `${tag}: kept, not removed`); + assert.equal(wolfShadows(w), 0, `${tag}: 狼影 0`); + assert.equal(u.trait.reinforcement, w, `${tag}: still his 援军`); + assert.ok(h.b.isReservedTile(tile[0], tile[1]), `${tag}: its tile stays taken`); + assert.deepEqual(w.blocking, [], `${tag}: no block`); + assert.notEqual(e.blockedBy, w); + const bites0 = h.hooksOf('attack').filter((c) => c.attacker === w).length; + h.run(iv - 0.5); + assert.equal(w.alive, false, `${tag}: still in its 战术点形态 (not back after the token's 10 s redeploy time)`); + assert.equal(h.hooksOf('attack').filter((c) => c.attacker === w).length, bites0, `${tag}: no attack`); + assert.notEqual(e.blockedBy, w, `${tag}: blocks nothing`); + assert.ok(h.runUntil(() => w.alive, 1), `${tag}: back`); + close(h.b.time - t0, iv, 0.1, `${tag}: after the 狼影 interval`); + assert.equal(wolfTacticalPoint(w), null); + assert.deepEqual([w.tileR, w.tileC], tile, `${tag}: on its tile`); + assert.equal(w.hp, w.s.maxHp, `${tag}: full HP`); + assert.equal(wolfShadows(w), 1, `${tag}: one 狼影`); + assert.equal(w.s.blockCnt, 1); + assert.equal(u.trait.reinforcement, w, `${tag}: the same pack`); + // a fresh 狼影 cycle: the next one an interval after the return + const t1 = h.b.time; + h.run(iv - 0.5); + assert.equal(wolfShadows(w), 1, `${tag}: no 狼影 before a full interval`); + assert.ok(h.runUntil(() => wolfShadows(w) === 2, 1), `${tag}: the next 狼影`); + close(h.b.time - t1, iv, 0.1, `${tag}: one interval after the return`); + done(h); + } +}); + +test('3_19 伺夜 S1 领袖的呼唤 by the pack\'s state (PRTS 备注 ①②③): +cost DP each cast; ② +1 狼影 (own cycle untouched), ③ at the maximum HP to max, ① from the 战术点形态 back at once with one 狼影 and a fresh cycle — a stale return timer never revives it (kit pack and prep piece)', () => { + for (const id of BOTH('chess_char_3_19_a')) for (const piece of [false, true]) { + const b = SB(id, 'skchr_vigil_1'), iv = WOLF_IV(id), tag = `${id} ${piece ? 'piece' : 'kit pack'}`; + const h = makeBattle({ defs: { chess: noGarrison(id) }, timeLimit: 120, flags: { dpPerSec: 0 }, units: vigilUnits(id, 'skchr_vigil_1', piece) }); + const u = h.unit(1), p = h.b.getPlayer('p1'); + h.step(); + const w = u.trait.reinforcement; + assert.ok(w && w.alive && (!piece || w === h.unit(2)), tag); + silence(h, u, true); // casts only when the test asks (the SP refills in 24–27 s) + const cast = () => { + const n0 = u.skill.activations, dp0 = p.dp; + silence(h, u, false); + fill(u); + h.step(); + silence(h, u, true); + assert.equal(u.skill.activations, n0 + 1, `${tag}: cast`); + approx(p.dp, dp0 + b.cost, 1e-9, `${tag}: +cost DP`); + }; + // ② below the maximum, 5 s into the pack's cycle: one more 狼影; its own next one still comes at deploy + interval + assert.equal(wolfShadows(w), 2); + h.run(5); + cast(); + assert.equal(wolfShadows(w), 3, `${tag} ②: +1 狼影`); + assert.equal(w.s.blockCnt, 3); + h.b.dealDamage(null, w, { amount: 1e9, type: 'true' }); + assert.equal(wolfShadows(w), 2); + assert.ok(h.runUntil(() => wolfShadows(w) === 3, iv), `${tag} ②: the pack's own 狼影`); + close(h.b.time - w.deployedAt, iv, 0.1, `${tag} ②: on the cycle from its deployment, not from the cast`); + // ③ at the maximum: the pack's HP back to max, the count unchanged + w.hp = w.s.maxHp * 0.3; + cast(); + assert.equal(wolfShadows(w), 3, `${tag} ③: still at the maximum`); + assert.equal(w.hp, w.s.maxHp, `${tag} ③: HP to max`); + // ① in its 战术点形态: back at once, one 狼影, full HP, a fresh cycle + for (let i = 0; i < 3; i++) h.b.dealDamage(null, w, { amount: 1e9, type: 'true' }); + assert.ok(!w.alive && wolfTacticalPoint(w), `${tag}: 战术点形态`); + h.run(3); + assert.equal(w.alive, false, `${tag}: waits in its 战术点形态 without a cast`); + cast(); + assert.ok(w.alive, `${tag} ①: back at once`); + assert.equal(wolfTacticalPoint(w), null); + assert.equal(wolfShadows(w), 1, `${tag} ①: one 狼影`); + assert.equal(w.hp, w.s.maxHp, `${tag} ①: full HP`); + assert.equal(u.trait.reinforcement, w, `${tag} ①: the same pack`); + const tUp = h.b.time; + assert.ok(h.runUntil(() => wolfShadows(w) === 2, iv + 1), `${tag} ①: the next 狼影`); + close(h.b.time - tUp, iv, 0.1, `${tag} ①: a fresh cycle from the return`); + // a stale timer: down, back by S1 3 s later, down again 2 s after that — the first form's return time passes + // without reviving it; it comes back an interval after the second knock-out + for (let i = 0; i < 2; i++) h.b.dealDamage(null, w, { amount: 1e9, type: 'true' }); + assert.ok(wolfTacticalPoint(w)); + const tDown = h.b.time; + h.run(3); + cast(); + assert.ok(w.alive); + h.run(2); + h.b.dealDamage(null, w, { amount: 1e9, type: 'true' }); + assert.ok(wolfTacticalPoint(w), `${tag}: 战术点形态 again`); + const tDown2 = h.b.time; + h.run(tDown + iv + 0.5 - h.b.time); + assert.equal(w.alive, false, `${tag}: the cancelled return does not revive it`); + assert.ok(h.runUntil(() => w.alive, 6), `${tag}: back`); + close(h.b.time - tDown2, iv, 0.1, `${tag}: an interval after the second knock-out`); + done(h); + } +}); + +test('3_19 伺夜 狼群: a 撤退 also ends in the 战术点形态 (狼影 0); 伺夜 leaving ends the form without a return — his redeploy brings a fresh pack; a standing pack leaves with him, no 战术点形态 (kit pack and prep piece)', () => { + const id = 'chess_char_3_19_a', iv = WOLF_IV(id); + for (const piece of [false, true]) { + const tag = piece ? 'piece' : 'kit pack'; + const h = makeBattle({ defs: { chess: noGarrison(id) }, timeLimit: 120, flags: { dpPerSec: 0 }, units: vigilUnits(id, null, piece) }); + h.step(); + const u = h.unit(1), w = u.trait.reinforcement; + assert.equal(wolfShadows(w), 2); + // a manual 撤退 (battle.retreat's default reason; no sim path withdraws the pack so today) + h.b.retreat(w); + assert.ok(!w.alive && wolfTacticalPoint(w), `${tag}: 撤退 ⇒ 战术点形态`); + assert.equal(wolfShadows(w), 0, `${tag}: 狼影 0`); + // 伺夜 leaves: the form ends there, no return + h.b.dealDamage(null, u, { amount: 1e9, type: 'true' }); + assert.equal(u.alive, false); + assert.equal(wolfTacticalPoint(w), null, `${tag}: the form ends with 伺夜`); + h.run(iv + 2); + assert.equal(w.alive, false, `${tag}: no return without 伺夜`); + // his redeploy: a fresh pack with the initial 狼影 + assert.ok(h.b.redeploy(u, { free: true })); + h.step(); const w2 = u.trait.reinforcement; - assert.ok(w2 && w2.alive, 'the pack is back'); - approx(p.dp, dp0 + b.cost, 1e-9, '+cost DP'); - assert.equal(w2.mem.wolves, 3, 'one more 狼影 on the returned pack'); + assert.ok(w2 && w2.alive, `${tag}: a pack with him`); + assert.equal(wolfShadows(w2), 2, `${tag}: the initial 狼影`); + assert.equal(h.b.allyUnits.filter((t) => t.alive && t.defId === WOLF).length, 1, `${tag}: one pack`); + // a standing pack leaves with him ('expired'): gone, no 战术点形态 + h.b.dealDamage(null, u, { amount: 1e9, type: 'true' }); + assert.equal(w2.alive, false); + assert.equal(wolfTacticalPoint(w2), null, `${tag}: no 战术点形态 when it leaves with 伺夜`); + assert.equal(w2.removed, true); done(h); } }); @@ -857,28 +1016,33 @@ test('3_19 伺夜 S2 领袖的馈赠: +DP, the pack recovers HP, its next attack } }); -test('3_19 伺夜 S2 领袖的馈赠 (自动触发): no enemy needed — cast at full SP while the pack stands and holds no unused gift (PRTS 备注), never without the pack', () => { - for (const id of BOTH('chess_char_3_19_a')) { - const b = SB(id, 'skchr_vigil_2'); - const h = makeBattle({ defs: { chess: noGarrison(id) }, timeLimit: 60, flags: { dpPerSec: 0 }, units: [U(id, 'skchr_vigil_2', 10, 3)] }); - const u = h.unit(id), p = h.b.getPlayer('p1'); +test('3_19 伺夜 S2 领袖的馈赠 (自动触发): no enemy needed — cast at full SP while the pack stands and holds no unused gift (PRTS 备注), never while it is in its 战术点形态 [ASSUMED] — cast once it is back (kit pack and prep piece)', () => { + for (const id of BOTH('chess_char_3_19_a')) for (const piece of [false, true]) { + const b = SB(id, 'skchr_vigil_2'), tag = `${id} ${piece ? 'piece' : 'kit pack'}`; + const h = makeBattle({ defs: { chess: noGarrison(id) }, timeLimit: 60, flags: { dpPerSec: 0 }, units: vigilUnits(id, 'skchr_vigil_2', piece) }); + const u = h.unit(1), p = h.b.getPlayer('p1'); h.run(1); const w = u.trait.reinforcement; - assert.ok(w && w.alive, 'the pack stands'); + assert.ok(w && w.alive, `${tag}: the pack stands`); const dp0 = p.dp; fill(u); - assert.ok(h.runUntil(() => u.skill.activations === 1, 1), 'cast with nobody on the field'); + assert.ok(h.runUntil(() => u.skill.activations === 1, 1), `${tag}: cast with nobody on the field`); approx(p.dp, dp0 + b.cost, 1e-6, '+cost DP at once'); assert.ok(w.mem.vigilGift, 'the pack holds the gift for its next attack'); fill(u); h.run(3); assert.equal(u.skill.activations, 1, '"狼群未获得此技能的充能时可触发": no second cast while the gift is unused'); - // the gift spent (as by a bite), the pack gone: still no cast until a pack stands again + // the gift spent (as by a bite), the pack in its 战术点形态: no cast (the 备注 does not count that form for S2 — + // [ASSUMED]); once it is back, the waiting cast fires w.mem.vigilGift = null; for (let i = 0; i < 10 && w.alive; i++) h.b.dealDamage(null, w, { amount: 1e9, type: 'true' }); // a wolf is lost per KO - assert.ok(!u.trait.reinforcement?.alive, 'no pack on the field'); - h.step(2); - assert.equal(u.skill.activations, 1, '"仅场上存在狼群…时可触发": no pack, no cast'); + assert.ok(wolfTacticalPoint(w), `${tag}: 战术点形态`); + h.run(5); + assert.equal(u.skill.activations, 1, `${tag}: no cast while the pack is in its 战术点形态`); + assert.ok(u.skill.ready, 'the SP waits full'); + assert.ok(h.runUntil(() => w.alive, WOLF_IV(id)), `${tag}: the pack is back`); + assert.ok(h.runUntil(() => u.skill.activations === 2, 0.5), `${tag}: cast once it stands again`); + assert.ok(w.mem.vigilGift, 'the returned pack holds the gift'); done(h); } }); diff --git a/test/content/kits_t3.test.js b/test/content/kits_t3.test.js index a416456a..c5cdac93 100644 --- a/test/content/kits_t3.test.js +++ b/test/content/kits_t3.test.js @@ -727,11 +727,17 @@ test('3_19 伺夜: wolf pack (2 → 3 wolves, block/bites, lose a wolf instead o assert.ok(g.runUntil(() => v.skill.active, 5)); g.runUntil(() => !v.skill.active, 20); approx(gp.dp, gdp + BB(gid).value, 1e-6, '精锐 DP over the full duration'); + // the last wolf falls: 战术点形态 (PRTS 狼群领袖 备注) for the 狼影 interval, then the same pack back with one wolf — + // not a new pack after the token's redeploy time (until 0.2.0) + const iv = ds.getToken('token_10028_vigil_wolf', gid).talents[0].bb.interval; g.b.dealDamage(null, gw, { type: 'true', amount: 1e7 }); g.b.dealDamage(null, gw, { type: 'true', amount: 1e7 }); assert.equal(gw.alive, false); g.run(gw.base.respawnTime + 0.5); - assert.ok(v.trait.reinforcement !== gw && v.trait.reinforcement?.alive, 'pack re-summoned after its respawn time'); + assert.equal(gw.alive, false, 'not back after the token’s redeploy time'); + assert.ok(g.runUntil(() => gw.alive, iv), 'back after the 狼影 interval'); + assert.equal(v.trait.reinforcement, gw, 'the same pack'); + assert.equal(gw.mem.wolves, 1, 'with one wolf'); done(g); }); diff --git a/test/content/tokens_devices.test.js b/test/content/tokens_devices.test.js index 4a932bc0..ea06b4e9 100644 --- a/test/content/tokens_devices.test.js +++ b/test/content/tokens_devices.test.js @@ -5,7 +5,7 @@ import assert from 'node:assert/strict'; import { makeBattle, chessRec, enemyRec, checkInvariants } from '../helpers/battleHarness.js'; import { hasGeneratedData, getDefaultSource } from '../../server/sim/simdata.js'; import { genericKit } from '../../server/sim/content/generic.js'; -import { spawnYanyou, spawnMapChar, TOKEN_IDS, wolfShadows, tileFree, findSummonTile, summonToken } from '../../server/sim/content/tokens.js'; +import { spawnYanyou, spawnMapChar, TOKEN_IDS, wolfShadows, wolfShadowInterval, wolfTacticalPoint, tileFree, findSummonTile, summonToken } from '../../server/sim/content/tokens.js'; import { startColdWind, kjeragColdWind, activateTurrets, terrainAt, deviceOverridesOf } from '../../server/sim/content/devices.js'; import { HUSK_REBIRTH } from '../../server/sim/content/enemies.js'; @@ -202,7 +202,7 @@ test('纸偶: appear burst = its ATK × damage_scale arts on the 8 surrounding t checkInvariants(h.b); }); -test('狼群: board piece becomes 伺夜\'s 援军; 2→3 狼影 (block & hits), fatal sheds a shadow, DEF ignore vs blocked, respawn', REAL, () => { +test('狼群: board piece becomes 伺夜\'s 援军; 2→3 狼影 (block & hits), fatal sheds a shadow, DEF ignore vs blocked, 战术点形态 after the last one', REAL, () => { let wolfHits = 0; const h = makeBattle({ defs: { enemies: { enemy_walker: walker({ def: 200, atk: 0 }) } }, @@ -238,11 +238,19 @@ test('狼群: board piece becomes 伺夜\'s 援军; 2→3 狼影 (block & hits), assert.equal(wolf.hp, wolf.s.maxHp); h.b.dealDamage(null, wolf, { amount: 1e6, type: 'true' }); h.b.dealDamage(null, wolf, { amount: 1e6, type: 'true' }); + // the last shadow falls: 战术点形态 (PRTS 狼群 备注) — off the field, kept, 0 狼影, for the 狼影 interval (data), then + // back on its tile with one 狼影 (no longer a full pack after the token's redeploy time) assert.equal(wolf.alive, false, 'last shadow falls'); - const died = h.b.time; - assert.ok(h.runUntil(() => wolf.alive, 20), 'respawns'); - approx(h.b.time - died, wolf.base.respawnTime, 0.3); - assert.equal(wolfShadows(wolf), 2, 'fresh pack'); + assert.ok(wolfTacticalPoint(wolf), '战术点形态'); + assert.equal(wolf.removed, false); + assert.equal(wolfShadows(wolf), 0); + const died = h.b.time, iv = wolfShadowInterval(wolf.def); + assert.equal(iv, tokDef(TOKEN_IDS.wolfPack, 'chess_char_3_19_b').talents[0].bb.interval, 'the talent interval'); + assert.ok(h.runUntil(() => wolf.alive, iv + 1), 'back'); + approx(h.b.time - died, iv, 0.004); + assert.deepEqual([wolf.tileR, wolf.tileC], [9, 6], 'on its tile'); + assert.equal(wolfShadows(wolf), 1, 'one 狼影'); + assert.equal(wolf.s.blockCnt, 1); checkInvariants(h.b); }); diff --git a/test/golden/bonds.json b/test/golden/bonds.json index f3c3c318..64329b0d 100644 --- a/test/golden/bonds.json +++ b/test/golden/bonds.json @@ -1163,34 +1163,34 @@ "round": 10, "seed": 1111632671, "waveId": "act1autochess_h03", - "time": 76.4, - "ticks": 2292, + "time": 74.33, + "ticks": 2230, "reason": "cleared", "killed": 26, "total": 26, "unspawned": 0, "bossHpLeft": null, - "rngDraws": 365, + "rngDraws": 380, "errors": {"count":0,"kinds":[]}, - "players": {"p1":{"killed":26,"total":26,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":321718,"bossDmg":0,"heal":929,"deaths":1,"layerGains":{"miraShip":6}}}, + "players": {"p1":{"killed":26,"total":26,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":323078,"bossDmg":0,"heal":929,"deaths":1,"layerGains":{"miraShip":6}}}, "units": [ - ["p1",1,"op","chess_char_1_08_b",1,"uniequip_002_texas",2924.8,1,7,1,16495,0,0,17,3], - ["p1",2,"op","chess_char_2_16_b",1,"uniequip_002_whitew",2304,1,8,4,82108,0,0,50,1], + ["p1",1,"op","chess_char_1_08_b",1,"uniequip_002_texas",2924.8,1,7,1,17028,0,0,19,3], + ["p1",2,"op","chess_char_2_16_b",1,"uniequip_002_whitew",2304,1,8,4,94384,0,0,49,1], ["p1",3,"op","chess_char_3_18_b",2,"uniequip_002_vulpis",2020,1,6,0,0,0,0,0,0], - ["p1",4,"op","chess_char_5_01_b",1,"uniequip_002_excu2",1428.96,1,9,0,0,885,0,0,0], - ["p1",5,"op","chess_char_1_07_b",0,"uniequip_002_prove",1527,1,3,7,46217,0,0,40,0], - ["p1",6,"op","chess_char_3_19_b",2,"uniequip_002_vigil",1798,1,4,14,174101,0,0,141,2], + ["p1",4,"op","chess_char_5_01_b",1,"uniequip_002_excu2",619.57,1,9,0,0,1694,0,0,0], + ["p1",5,"op","chess_char_1_07_b",0,"uniequip_002_prove",1527,1,3,9,55294,0,0,48,0], + ["p1",6,"op","chess_char_3_19_b",2,"uniequip_002_vigil",1798,1,4,12,155534,0,0,143,2], ["p1",7,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,10,0,0,0,0,0,0], ["p1",8,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",0,0,11,0,0,2451,929,2,0], - ["p1",9,"token","token_10028_vigil_wolf",null,null,0,0,37,0,2796,5814,0,4,null], + ["p1",9,"token","token_10028_vigil_wolf",null,null,446.88,1,40,0,838,3477,0,2,null], ["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null], ["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null] ], - "enemies": {"enemy_1158_divman":[20,20,0,7809,164229],"enemy_1160_hvyslr_2":[3,3,0,0,104509],"enemy_1161_tidmag_2":[3,3,0,1341,59720]}, - "hooks": [1,39,2292,311,311,481,484,2,30,30,6,5,0,3813,40,4,26,0,1,35,3,0,0], - "events": {"atk":312,"deploy":11,"die":30,"dmg":484,"fx":26,"heal":2,"layer":3,"skill":11,"spawn":37,"status":73}, - "fx": {"aoe":3,"bondProc":8,"crit":5,"dp":4,"garrison":3,"wolfShadowLost":3}, - "snaps": "d1ead0d3 cda7c00c 8843b429 bd54e255 1de8d432 e6f225a5 7cba25d9 d80a750d" + "enemies": {"enemy_1158_divman":[20,20,0,6116,164229],"enemy_1160_hvyslr_2":[3,3,0,0,104509],"enemy_1161_tidmag_2":[3,3,0,1507,59720]}, + "hooks": [1,39,2230,313,313,469,473,2,29,29,6,5,0,3754,38,2,26,0,1,31,3,0,0], + "events": {"atk":314,"deploy":11,"die":29,"dmg":473,"fx":29,"heal":2,"layer":3,"skill":11,"spawn":37,"status":75}, + "fx": {"aoe":3,"bondProc":9,"crit":5,"dp":4,"garrison":3,"wolfShadow":2,"wolfShadowLost":3}, + "snaps": "d1ead0d3 cda7c00c ccc1aada c0883850 2ba76678 a36bad1d 085c7f88 f7baafc7" }, "bond-skillfulShip-low": { "about": "灵巧 low: tier 1 layers 1; chess_char_3_12_a chess_char_1_14_a chess_char_2_04_a chess_char_3_08_a chess_char_3_09_a chess_char_3_10_a chess_char_3_11_a chess_char_3_14_a", diff --git a/test/golden/matches.json b/test/golden/matches.json index d3a143d2..9f96658c 100644 --- a/test/golden/matches.json +++ b/test/golden/matches.json @@ -99,7 +99,7 @@ "combat": "client", "boost": null, "setup": {"stageId":"act1autochess_m03","bossId":"boss_5","hiddenBossId":"boss_8","factions":["TIMES","DOT","INVISIBLE"]}, - "end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":11,"bossPoolLeft":0,"virtualMs":106900}, + "end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":10,"bossPoolLeft":0,"virtualMs":106900}, "rng": {"setup":25,"shop":207,"waves":0,"draft":10,"bots":8483,"meta":14}, "errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0}, "rounds": [ @@ -118,27 +118,27 @@ }, { "round": 8, - "ai_0": [1,21,11,0,0,4,8,"9642d3a4",62,"deputShip 1/6, emptyShip 1/2, kazimierzShip 0/8, kjeragShip 1/9, preciShip 0/5, sargonShip 0/8, siracusaShip 1/31, soloShip 1/14, steadShip 0/6, swiftShip 0/8",13,19,6,60] + "ai_0": [1,20,11,0,0,4,8,"9642d3a4",62,"deputShip 1/6, emptyShip 1/2, kazimierzShip 0/8, kjeragShip 1/9, preciShip 0/5, sargonShip 0/8, siracusaShip 1/31, soloShip 1/14, steadShip 0/6, swiftShip 0/8",12,19,7,60] }, { "round": 9, - "ai_0": [1,21,12,0,0,4,8,"765a4a23",99,"arcaneShip 0/2, deputShip 1/8, emptyShip 1/4, kazimierzShip 0/8, kjeragShip 0/14, preciShip 0/12, sargonShip 0/8, siracusaShip 1/53, soloShip 1/16, steadShip 0/6, suntShip 1/2, swiftShip 1/16",18,18,0,73] + "ai_0": [1,20,12,0,0,4,8,"765a4a23",99,"arcaneShip 0/2, deputShip 1/8, emptyShip 1/4, kazimierzShip 0/8, kjeragShip 0/14, preciShip 0/12, sargonShip 0/8, siracusaShip 1/53, soloShip 1/16, steadShip 0/6, suntShip 1/2, swiftShip 1/16",18,18,0,73] }, { "round": 10, - "ai_0": [1,21,12,0,0,5,8,"44e95a01",112,"arcaneShip 0/2, deputShip 0/8, emptyShip 0/4, kazimierzShip 0/8, kjeragShip 0/14, miraShip 1/3, preciShip 0/12, sargonShip 0/8, siracusaShip 2/69, soloShip 1/16, steadShip 0/6, suntShip 1/2, swiftShip 1/22",24,24,0,87] + "ai_0": [1,20,12,0,0,5,8,"44e95a01",112,"arcaneShip 0/2, deputShip 0/8, emptyShip 0/4, kazimierzShip 0/8, kjeragShip 0/14, miraShip 1/3, preciShip 0/12, sargonShip 0/8, siracusaShip 2/69, soloShip 1/16, steadShip 0/6, suntShip 1/2, swiftShip 1/22",24,24,0,87] }, { "round": 11, - "ai_0": [1,21,12,0,0,5,8,"12000646",141,"arcaneShip 0/2, deputShip 0/8, emptyShip 0/4, kazimierzShip 0/8, kjeragShip 0/14, miraShip 1/6, preciShip 0/12, sargonShip 0/8, siracusaShip 2/95, soloShip 1/16, steadShip 0/6, suntShip 1/2, swiftShip 1/22",24,24,0,98] + "ai_0": [1,20,12,0,0,5,8,"12000646",141,"arcaneShip 0/2, deputShip 0/8, emptyShip 0/4, kazimierzShip 0/8, kjeragShip 0/14, miraShip 1/6, preciShip 0/12, sargonShip 0/8, siracusaShip 2/95, soloShip 1/16, steadShip 0/6, suntShip 1/2, swiftShip 1/22",24,24,0,98] }, { "round": 12, - "ai_0": [1,11,12,0,0,6,8,"03cea672",154,"arcaneShip 0/2, deputShip 0/8, emptyShip 0/4, kazimierzShip 0/8, kjeragShip 0/14, miraShip 1/9, preciShip 0/12, sargonShip 0/8, siracusaShip 2/105, soloShip 1/16, steadShip 0/6, suntShip 1/2, swiftShip 1/22",16,31,15,109] + "ai_0": [1,10,12,0,0,6,8,"03cea672",154,"arcaneShip 0/2, deputShip 0/8, emptyShip 0/4, kazimierzShip 0/8, kjeragShip 0/14, miraShip 1/9, preciShip 0/12, sargonShip 0/8, siracusaShip 2/105, soloShip 1/16, steadShip 0/6, suntShip 1/2, swiftShip 1/22",17,31,14,109] }, { "round": 13, - "ai_0": [1,11,12,0,0,6,8,"1cf42739",230,"arcaneShip 0/2, deputShip 2/12, emptyShip 1/8, kazimierzShip 0/16, kjeragShip 0/22, miraShip 1/13, preciShip 0/12, sargonShip 0/8, siracusaShip 1/129, soloShip 1/20, steadShip 0/6, suntShip 1/6, swiftShip 1/42",115,115,0,121] + "ai_0": [1,10,12,0,0,6,8,"1cf42739",230,"arcaneShip 0/2, deputShip 2/12, emptyShip 1/8, kazimierzShip 0/16, kjeragShip 0/22, miraShip 1/13, preciShip 0/12, sargonShip 0/8, siracusaShip 1/129, soloShip 1/20, steadShip 0/6, suntShip 1/6, swiftShip 1/42",115,115,0,121] } ], "players": { @@ -147,12 +147,12 @@ "victory": true, "roundsPassed": 14, "eliminatedRound": null, - "lp": 11, + "lp": 10, "bandId": "band_ermengard", "title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"}, "trophies": 0, "reward": 400, - "stats": {"activatedLayers":290,"bossDamage":97500,"dmgDealt":1700976,"gold":131,"itemsEquipped":23,"kills":361,"leaks":21,"lpLost":16,"merges":5,"perfectRounds":11,"refreshes":15}, + "stats": {"activatedLayers":290,"bossDamage":97500,"dmgDealt":1691540,"gold":131,"itemsEquipped":23,"kills":361,"leaks":21,"lpLost":17,"merges":5,"perfectRounds":11,"refreshes":15}, "lineup": ["2_14_a@12,5","5_10_a@12,7[4_07_e_a+3_08_e_b]","5_20_b@11,5[6_09_e_a+5_02_e_b]","3_20_b@11,8[3_06_e_a+4_08_e_a]","6_11_a@10,3[4_12_e_a+5_02_e_b]","3_18_a@10,8[2_04_e_b+4_05_e_b]","6_19_a@9,4[4_04_e_a+4_10_e_a]","1_08_a@9,5[1_01_e_a+1_02_e_a]"], "bonds": ["arcaneShip 0/2","deputShip 1/14","emptyShip 1/10","kazimierzShip 0/16","kjeragShip 1/24","maniShip 1/2","miraShip 1/15","preciShip 0/12","sargonShip 0/8","siracusaShip 1/151","soloShip 1/22","steadShip 0/6","suntShip 1/8","swiftShip 1/44"] } @@ -511,7 +511,7 @@ "combat": "client", "boost": null, "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":121400}, + "end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":54,"bossPoolLeft":0,"virtualMs":121400}, "rng": {"setup":25,"shop":449,"waves":0,"draft":40,"bots":21634,"meta":8}, "errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0}, "rounds": [ @@ -577,7 +577,7 @@ }, { "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,36,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",25,34,9,125], "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] } ], @@ -587,12 +587,12 @@ "victory": true, "roundsPassed": 14, "eliminatedRound": null, - "lp": 37, + "lp": 36, "bandId": "band_sarkazb", "title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"}, "trophies": 4, "reward": 400, - "stats": {"activatedLayers":250,"bossDamage":287418,"dmgDealt":3020895,"gold":139,"itemsEquipped":12,"kills":334,"leaks":9,"lpLost":8,"merges":5,"perfectRounds":11,"refreshes":23}, + "stats": {"activatedLayers":250,"bossDamage":287418,"dmgDealt":2992890,"gold":139,"itemsEquipped":12,"kills":333,"leaks":10,"lpLost":9,"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"], "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"] }, @@ -733,8 +733,8 @@ "combat": "client", "boost": null, "setup": {"stageId":"act2autochess_m03","bossId":"boss_4","hiddenBossId":"boss_9","factions":["FLY","TIMES","REFLECTION"]}, - "end": {"victory":false,"roundsPassed":11,"hiddenReached":false,"hiddenCleared":false,"reason":"eliminated","teamLp":null,"bossPoolLeft":null,"virtualMs":115700}, - "rng": {"setup":25,"shop":537,"waves":0,"draft":75,"bots":24773,"meta":19}, + "end": {"victory":false,"roundsPassed":12,"hiddenReached":false,"hiddenCleared":false,"reason":"eliminated","teamLp":null,"bossPoolLeft":null,"virtualMs":124350}, + "rng": {"setup":25,"shop":606,"waves":0,"draft":76,"bots":26471,"meta":24}, "errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0}, "rounds": [ { @@ -789,25 +789,31 @@ "round": 9, "ai_0": [1,8,12,0,6,4,8,"6d558be7",157,"deputShip 2/32, indomShip 1/4, kazimierzShip 0/2, kjeragShip 1/28, skillfulShip 1/51, soloShip 1/16, steadShip 1/22, suntShip 1/4, swiftShip 0/20",65,68,3,81], "ai_1": [1,24,12,0,1,4,8,"ad1959de",111,"deputShip 2/35, kazimierzShip 0/7, skillfulShip 1/22, soloShip 1/16, suntShip 1/16, swiftShip 0/21, victoriaShip 1/22",41,52,11,73], - "ai_2": [1,7,12,0,0,4,8,"e0957cd2",127,"deputShip 0/10, miraShip 1/3, siracusaShip 2/78, soloShip 1/22, suntShip 1/6, swiftShip 1/18, yanShip 0/1",51,60,9,75] + "ai_2": [1,13,12,0,0,4,8,"790ec77b",127,"deputShip 0/10, miraShip 1/3, siracusaShip 2/78, soloShip 1/22, suntShip 1/6, swiftShip 1/18, yanShip 0/1",59,62,3,75] }, { "round": 10, - "ai_0": [1,8,18,0,0,5,8,"7365b613",207,"deputShip 2/36, indomShip 1/8, kazimierzShip 0/2, kjeragShip 1/41, miraShip 1/4, skillfulShip 1/57, soloShip 1/20, steadShip 1/33, suntShip 1/8, swiftShip 0/20",18,18,0,99], - "ai_1": [1,15,13,0,0,5,8,"776e574d",124,"deputShip 2/40, indomShip 1/5, kazimierzShip 0/7, skillfulShip 1/26, soloShip 1/20, suntShip 0/16, swiftShip 0/21, victoriaShip 1/33",3,18,15,86], - "ai_2": [0,0,12,0,0,5,0,"811c9dc5",0,"deputShip 0/10, miraShip 0/5, siracusaShip 0/84, soloShip 0/22, suntShip 0/6, swiftShip 0/23, yanShip 0/1",5,18,13,86] + "ai_0": [1,8,18,0,0,5,8,"0cdaabe2",207,"deputShip 2/36, indomShip 1/8, kazimierzShip 0/2, kjeragShip 1/41, miraShip 1/4, skillfulShip 1/57, soloShip 1/20, steadShip 1/33, suntShip 1/8, swiftShip 0/20",18,18,0,99], + "ai_1": [1,20,13,0,0,5,8,"5553a152",132,"deputShip 2/40, indomShip 1/9, kazimierzShip 0/7, skillfulShip 1/26, soloShip 1/20, suntShip 0/16, swiftShip 0/21, victoriaShip 1/37",9,18,9,86], + "ai_2": [1,7,12,0,2,5,8,"d71a0d82",137,"deputShip 0/10, miraShip 1/4, siracusaShip 2/82, soloShip 1/22, suntShip 1/6, swiftShip 1/23, yanShip 0/1",9,18,9,86] }, { "round": 11, - "ai_0": [1,3,12,0,1,5,8,"d2be236f",254,"deputShip 2/40, indomShip 1/12, kazimierzShip 0/2, kjeragShip 1/51, miraShip 1/8, siracusaShip 0/6, skillfulShip 1/63, soloShip 1/24, steadShip 1/44, suntShip 1/12, swiftShip 0/26",20,25,5,110], - "ai_1": [1,5,12,0,0,5,8,"3d8f81b8",150,"deputShip 1/42, indomShip 1/11, kazimierzShip 0/7, skillfulShip 1/28, soloShip 0/20, suntShip 0/16, swiftShip 0/21, victoriaShip 2/69, yanShip 0/5",7,25,18,98], - "ai_2": [0,0,0,0,0,5,0,"811c9dc5",0,"deputShip 0/10, miraShip 0/5, siracusaShip 0/84, soloShip 0/22, suntShip 0/6, swiftShip 0/23, yanShip 0/1",null,null,null,86] + "ai_0": [1,3,12,0,1,5,8,"15e6bba0",255,"deputShip 2/40, indomShip 1/12, kazimierzShip 0/2, kjeragShip 1/52, miraShip 1/8, siracusaShip 0/6, skillfulShip 1/63, soloShip 1/24, steadShip 1/44, suntShip 1/12, swiftShip 0/26",20,25,5,110], + "ai_1": [1,14,12,0,1,5,8,"458bff99",175,"deputShip 2/44, indomShip 1/15, kazimierzShip 0/11, skillfulShip 1/30, soloShip 1/24, steadShip 1/8, suntShip 0/16, swiftShip 0/25, victoriaShip 1/54",19,25,6,100], + "ai_2": [0,0,14,0,0,5,0,"811c9dc5",0,"arcaneShip 0/16, deputShip 0/10, miraShip 0/5, siracusaShip 0/100, soloShip 0/22, suntShip 0/6, swiftShip 0/33, yanShip 0/1",5,25,20,101] }, { "round": 12, - "ai_0": [0,0,13,0,0,6,0,"811c9dc5",0,"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",16,57,41,124], - "ai_1": [0,0,12,0,0,6,0,"811c9dc5",0,"deputShip 0/44, indomShip 0/19, kazimierzShip 0/7, skillfulShip 0/30, soloShip 0/20, suntShip 0/16, swiftShip 0/21, victoriaShip 0/102, yanShip 0/5",24,57,33,109], - "ai_2": [0,0,0,0,0,5,0,"811c9dc5",0,"deputShip 0/10, miraShip 0/5, siracusaShip 0/84, soloShip 0/22, suntShip 0/6, swiftShip 0/23, yanShip 0/1",null,null,null,86] + "ai_0": [0,0,13,0,0,6,0,"811c9dc5",0,"deputShip 0/44, indomShip 0/16, kazimierzShip 0/2, kjeragShip 0/61, miraShip 0/12, siracusaShip 0/6, skillfulShip 0/69, soloShip 0/28, steadShip 0/55, suntShip 0/16, swiftShip 0/26",14,57,43,122], + "ai_1": [1,4,13,0,0,6,8,"fedd02aa",227,"deputShip 2/48, indomShip 1/23, kazimierzShip 0/11, kjeragShip 0/5, skillfulShip 1/39, soloShip 1/28, steadShip 1/16, suntShip 0/16, swiftShip 0/25, victoriaShip 1/73",17,57,40,113], + "ai_2": [0,0,0,0,0,5,0,"811c9dc5",0,"arcaneShip 0/16, deputShip 0/10, miraShip 0/5, siracusaShip 0/100, soloShip 0/22, suntShip 0/6, swiftShip 0/33, yanShip 0/1",null,null,null,101] + }, + { + "round": 13, + "ai_0": [0,0,0,0,0,6,0,"811c9dc5",0,"deputShip 0/44, indomShip 0/16, kazimierzShip 0/2, kjeragShip 0/61, miraShip 0/12, siracusaShip 0/6, skillfulShip 0/69, soloShip 0/28, steadShip 0/55, suntShip 0/16, swiftShip 0/26",null,null,null,122], + "ai_1": [0,0,12,0,0,6,0,"811c9dc5",0,"deputShip 0/52, indomShip 0/27, kazimierzShip 0/11, kjeragShip 0/5, skillfulShip 0/43, soloShip 0/32, steadShip 0/26, suntShip 0/16, swiftShip 0/25, victoriaShip 0/89",5,26,21,124], + "ai_2": [0,0,0,0,0,5,0,"811c9dc5",0,"arcaneShip 0/16, deputShip 0/10, miraShip 0/5, siracusaShip 0/100, soloShip 0/22, suntShip 0/6, swiftShip 0/33, yanShip 0/1",null,null,null,101] } ], "players": { @@ -818,40 +824,40 @@ "eliminatedRound": 12, "lp": 0, "bandId": "band_chiave", - "title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"}, + "title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"}, "trophies": 2, "reward": 255, - "stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":4777308,"gold":124,"itemsEquipped":17,"kills":279,"leaks":70,"lpLost":30,"merges":3,"perfectRounds":4,"refreshes":14}, + "stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":4492000,"gold":122,"itemsEquipped":17,"kills":275,"leaks":72,"lpLost":30,"merges":3,"perfectRounds":4,"refreshes":15}, "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/61","miraShip 0/12","siracusaShip 0/6","skillfulShip 0/69","soloShip 0/28","steadShip 0/55","suntShip 0/16","swiftShip 0/26"] }, "ai_1": { "alive": false, "victory": false, - "roundsPassed": 11, - "eliminatedRound": 12, + "roundsPassed": 12, + "eliminatedRound": 13, "lp": 0, "bandId": "band_sarkazb", "title": {"id":"comment_6","name":"挥金如土","picId":"comment_icon_6","text":"消耗资金最多"}, - "trophies": 2, - "reward": 255, - "stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2690412,"gold":109,"itemsEquipped":9,"kills":258,"leaks":92,"lpLost":50,"merges":3,"perfectRounds":5,"refreshes":18}, + "trophies": 3, + "reward": 298, + "stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":3467151,"gold":124,"itemsEquipped":13,"kills":274,"leaks":102,"lpLost":51,"merges":3,"perfectRounds":5,"refreshes":20}, "lineup": [], - "bonds": ["deputShip 0/44","indomShip 0/19","kazimierzShip 0/7","skillfulShip 0/30","soloShip 0/20","suntShip 0/16","swiftShip 0/21","victoriaShip 0/102","yanShip 0/5"] + "bonds": ["deputShip 0/52","indomShip 0/27","kazimierzShip 0/11","kjeragShip 0/5","skillfulShip 0/43","soloShip 0/32","steadShip 0/26","suntShip 0/16","swiftShip 0/25","victoriaShip 0/89"] }, "ai_2": { "alive": false, "victory": false, - "roundsPassed": 9, - "eliminatedRound": 10, + "roundsPassed": 10, + "eliminatedRound": 11, "lp": 0, "bandId": "band_lmlee", - "title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"}, + "title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"}, "trophies": 2, - "reward": 191, - "stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1500871,"gold":86,"itemsEquipped":5,"kills":199,"leaks":45,"lpLost":29,"merges":2,"perfectRounds":4,"refreshes":14}, + "reward": 213, + "stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1915977,"gold":101,"itemsEquipped":9,"kills":216,"leaks":55,"lpLost":30,"merges":4,"perfectRounds":4,"refreshes":18}, "lineup": [], - "bonds": ["deputShip 0/10","miraShip 0/5","siracusaShip 0/84","soloShip 0/22","suntShip 0/6","swiftShip 0/23","yanShip 0/1"] + "bonds": ["arcaneShip 0/16","deputShip 0/10","miraShip 0/5","siracusaShip 0/100","soloShip 0/22","suntShip 0/6","swiftShip 0/33","yanShip 0/1"] } } }, @@ -1181,8 +1187,8 @@ "combat": "server", "boost": null, "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":114800}, - "rng": {"setup":25,"shop":383,"waves":0,"draft":34,"bots":16823,"meta":105}, + "end": {"victory":false,"roundsPassed":12,"hiddenReached":false,"hiddenCleared":false,"reason":"eliminated","teamLp":null,"bossPoolLeft":null,"virtualMs":113300}, + "rng": {"setup":25,"shop":389,"waves":0,"draft":34,"bots":16823,"meta":81}, "errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0}, "rounds": [ { @@ -1217,68 +1223,68 @@ }, { "round": 7, - "ai_0": [1,19,13,0,0,4,8,"28ba7985",39,"arcaneShip 0/2, kjeragShip 0/2, preciShip 1/2, siracusaShip 1/20, soloShip 1/11, steadShip 0/2, swiftShip 1/6",11,27,16,52], + "ai_0": [1,19,13,0,0,4,8,"bfa893e9",41,"arcaneShip 0/2, kjeragShip 0/2, preciShip 1/2, siracusaShip 1/22, soloShip 1/11, steadShip 0/2, swiftShip 1/6",12,27,15,52], "ai_1": [1,18,11,0,1,4,8,"eac0b2d5",6,"arcaneShip 0/2, kjeragShip 0/4, preciShip 1/2, siracusaShip 1/0, soloShip 1/0, steadShip 2/4",16,27,11,51] }, { "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",62,"arcaneShip 1/6, emptyShip 1/0, kjeragShip 0/7, preciShip 1/7, siracusaShip 1/32, soloShip 1/11, steadShip 0/2, swiftShip 1/6",23,23,0,64], "ai_1": [1,16,12,0,0,4,8,"2243ec88",26,"arcaneShip 1/8, indomShip 1/1, kjeragShip 0/4, preciShip 0/2, siracusaShip 1/10, soloShip 1/0, steadShip 2/7",18,23,5,61] }, { "round": 9, - "ai_0": [1,19,12,0,1,4,8,"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,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] + "ai_0": [1,19,12,0,1,4,8,"6dfc43a6",74,"arcaneShip 2/12, emptyShip 1/0, indomShip 0/8, kjeragShip 0/7, preciShip 1/7, siracusaShip 1/38, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",16,16,0,77], + "ai_1": [1,16,12,0,0,4,8,"bbe510e7",82,"arcaneShip 1/16, indomShip 1/10, kjeragShip 0/8, preciShip 1/22, siracusaShip 1/20, soloShip 1/0, steadShip 1/14, victoriaShip 0/8, visiShip 0/8",15,15,0,74] }, { "round": 10, - "ai_0": [1,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,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] + "ai_0": [1,18,13,0,0,5,8,"e24edc84",78,"arcaneShip 2/14, emptyShip 1/0, indomShip 0/8, kjeragShip 0/7, preciShip 1/7, siracusaShip 1/40, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",23,24,1,89], + "ai_1": [1,16,12,0,0,5,8,"f2b57087",100,"arcaneShip 1/16, indomShip 1/12, kjeragShip 0/8, miraShip 0/3, preciShip 1/35, siracusaShip 1/20, soloShip 1/0, steadShip 1/17, suntShip 1/0, victoriaShip 0/8, visiShip 0/8, yanShip 0/6",23,23,0,86] }, { "round": 11, - "ai_0": [1,17,12,0,0,5,8,"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,8,12,0,0,5,8,"f020d91c",127,"arcaneShip 1/20, indomShip 1/30, 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_0": [1,9,12,0,0,5,8,"586b372b",90,"arcaneShip 2/20, emptyShip 1/0, indomShip 0/8, kjeragShip 0/7, preciShip 1/7, siracusaShip 1/46, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",14,23,9,101], + "ai_1": [1,12,12,0,0,5,8,"b5188f04",124,"arcaneShip 2/26, indomShip 1/14, kazimierzShip 0/8, kjeragShip 0/16, miraShip 0/3, preciShip 1/54, siracusaShip 1/30, soloShip 1/0, steadShip 0/17, suntShip 1/0, swiftShip 0/8, victoriaShip 0/8, visiShip 0/8, yanShip 0/6",19,23,4,99] }, { "round": 12, - "ai_0": [1,7,12,0,0,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": [0,0,12,0,0,6,0,"811c9dc5",0,"arcaneShip 0/20, indomShip 0/50, kjeragShip 0/8, preciShip 0/2, siracusaShip 0/58, steadShip 0/22, victoriaShip 0/8, visiShip 0/8",20,46,26,109] + "ai_0": [0,0,12,0,0,5,0,"811c9dc5",0,"arcaneShip 0/24, indomShip 0/8, kjeragShip 0/7, preciShip 0/7, siracusaShip 0/50, soloShip 0/11, steadShip 0/2, swiftShip 0/6, victoriaShip 0/8, visiShip 0/8",27,46,19,114], + "ai_1": [1,2,12,0,0,6,8,"f5a033f5",153,"arcaneShip 2/26, indomShip 0/14, kazimierzShip 0/8, kjeragShip 0/16, miraShip 0/3, preciShip 1/78, siracusaShip 1/30, soloShip 1/0, steadShip 1/19, suntShip 1/0, swiftShip 0/8, victoriaShip 0/8, visiShip 0/8, yanShip 0/6",27,46,19,111] }, { "round": 13, - "ai_0": [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,0,0,0,6,0,"811c9dc5",0,"arcaneShip 0/20, indomShip 0/50, kjeragShip 0/8, preciShip 0/2, siracusaShip 0/58, steadShip 0/22, victoriaShip 0/8, visiShip 0/8",null,null,null,109] + "ai_0": [0,0,0,0,0,5,0,"811c9dc5",0,"arcaneShip 0/24, indomShip 0/8, kjeragShip 0/7, preciShip 0/7, siracusaShip 0/50, soloShip 0/11, steadShip 0/2, swiftShip 0/6, victoriaShip 0/8, visiShip 0/8",null,null,null,114], + "ai_1": [0,0,12,0,0,6,0,"811c9dc5",0,"arcaneShip 0/32, indomShip 0/14, kazimierzShip 0/16, kjeragShip 0/24, miraShip 0/3, preciShip 0/102, siracusaShip 0/42, steadShip 0/19, swiftShip 0/16, victoriaShip 0/8, visiShip 0/8, yanShip 0/6",27,39,12,121] } ], "players": { "ai_0": { "alive": false, "victory": false, - "roundsPassed": 12, - "eliminatedRound": 13, + "roundsPassed": 11, + "eliminatedRound": 12, "lp": 0, "bandId": "band_doberm", "title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"}, - "trophies": 3, - "reward": 280, - "stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2828648,"gold":128,"itemsEquipped":14,"kills":230,"leaks":49,"lpLost":33,"merges":3,"perfectRounds":8,"refreshes":18}, + "trophies": 2, + "reward": 240, + "stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2035070,"gold":114,"itemsEquipped":13,"kills":194,"leaks":45,"lpLost":31,"merges":4,"perfectRounds":7,"refreshes":15}, "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/24","indomShip 0/8","kjeragShip 0/7","preciShip 0/7","siracusaShip 0/50","soloShip 0/11","steadShip 0/2","swiftShip 0/6","victoriaShip 0/8","visiShip 0/8"] }, "ai_1": { "alive": false, "victory": false, - "roundsPassed": 11, - "eliminatedRound": 12, + "roundsPassed": 12, + "eliminatedRound": 13, "lp": 0, "bandId": "band_malkie", "title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"}, - "trophies": 2, - "reward": 240, - "stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1891141,"gold":109,"itemsEquipped":16,"kills":179,"leaks":50,"lpLost":30,"merges":2,"perfectRounds":6,"refreshes":14}, + "trophies": 3, + "reward": 280, + "stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2655538,"gold":121,"itemsEquipped":18,"kills":217,"leaks":51,"lpLost":36,"merges":3,"perfectRounds":8,"refreshes":17}, "lineup": [], - "bonds": ["arcaneShip 0/20","indomShip 0/50","kjeragShip 0/8","preciShip 0/2","siracusaShip 0/58","steadShip 0/22","victoriaShip 0/8","visiShip 0/8"] + "bonds": ["arcaneShip 0/32","indomShip 0/14","kazimierzShip 0/16","kjeragShip 0/24","miraShip 0/3","preciShip 0/102","siracusaShip 0/42","steadShip 0/19","swiftShip 0/16","victoriaShip 0/8","visiShip 0/8","yanShip 0/6"] } } }, diff --git a/test/golden/roster.json b/test/golden/roster.json index d2643f33..49a27af1 100644 --- a/test/golden/roster.json +++ b/test/golden/roster.json @@ -387,55 +387,56 @@ "time": 140, "ticks": 4200, "reason": "timeout", - "killed": 112, - "total": 162, + "killed": 103, + "total": 165, "unspawned": 0, "bossHpLeft": null, "rngDraws": 64, "errors": {"count":0,"kinds":[]}, - "players": {"p1":{"killed":112,"total":162,"perfect":false,"leaks":50,"leaksUncounted":1,"coins":2,"dmg":202293,"bossDmg":0,"heal":650,"deaths":11,"layerGains":{}}}, + "players": {"p1":{"killed":103,"total":165,"perfect":false,"leaks":62,"leaksUncounted":1,"coins":4,"dmg":206927,"bossDmg":0,"heal":650,"deaths":11,"layerGains":{}}}, "units": [ - ["p1",1,"op","chess_char_3_12_a",2,null,2334,1,180,2,1060,2334,0,11,1], - ["p1",2,"op","chess_char_3_16_a",1,null,0,0,6,0,3135,2823,650,18,1], - ["p1",3,"op","chess_char_3_18_a",2,null,1481,1,186,6,10342,2962,0,29,1], - ["p1",4,"op","chess_char_3_08_a",1,null,1326,1,4,12,21498,268,0,20,2], - ["p1",5,"op","chess_char_3_09_a",1,null,739,1,7,18,20206,528,0,64,2], - ["p1",6,"op","chess_char_3_10_a",1,null,0,0,10,13,7811,1650,0,21,2], - ["p1",7,"op","chess_char_3_11_a",1,null,1316,1,187,14,11933,1316,0,22,3], - ["p1",8,"op","chess_char_3_13_a",1,null,1064,1,185,3,5201,1064,0,22,10], - ["p1",9,"op","chess_char_3_14_a",1,null,0,0,5,5,10307,1251,0,30,1], - ["p1",10,"op","chess_char_3_17_a",1,null,0,0,8,13,53646,2246,0,78,4], - ["p1",11,"op","chess_char_3_19_a",2,null,731,1,141,16,34429,2147,0,91,2], - ["p1",12,"op","chess_char_3_20_a",1,null,1121,1,189,2,5255,1121,0,40,1], - ["p1",13,"token","token_10028_vigil_wolf",null,null,880,1,188,8,17470,5205,0,47,null], + ["p1",1,"op","chess_char_3_12_a",2,null,2277.37,1,183,4,2509,2391,0,17,1], + ["p1",2,"op","chess_char_3_16_a",1,null,0,0,6,0,468,2823,650,29,1], + ["p1",3,"op","chess_char_3_18_a",2,null,1481,1,189,5,10012,2962,0,27,1], + ["p1",4,"op","chess_char_3_08_a",1,null,1594,1,4,12,28602,0,0,20,2], + ["p1",5,"op","chess_char_3_09_a",1,null,927,1,7,14,27278,340,0,63,2], + ["p1",6,"op","chess_char_3_10_a",1,null,0,0,10,14,14131,1650,0,25,2], + ["p1",7,"op","chess_char_3_11_a",1,null,1316,1,190,12,10443,1316,0,19,3], + ["p1",8,"op","chess_char_3_13_a",1,null,1064,1,186,3,4643,1064,0,21,9], + ["p1",9,"op","chess_char_3_14_a",1,null,0,0,5,11,14855,1251,0,49,2], + ["p1",10,"op","chess_char_3_17_a",1,null,0,0,8,8,43714,2246,0,62,4], + ["p1",11,"op","chess_char_3_19_a",2,null,731,1,147,12,32311,2147,0,87,2], + ["p1",12,"op","chess_char_3_20_a",1,null,1121,1,191,2,5153,1121,0,36,1], + ["p1",13,"token","token_10028_vigil_wolf",null,null,599.46,0,102,4,4175,1361,0,13,null], ["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null], - ["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null] + ["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null], + ["p1","~1","token","token_10028_vigil_wolf",null,null,0,0,148,2,8632,1697,0,19,null] ], "enemies": { "enemy_10029_vtshld": [1,0,0,0,0], "enemy_10073_mpcar": [1,1,0,0,2304], "enemy_10127_rkmbst_2": [1,0,1,4291,19874], - "enemy_1045_hammer": [1,0,1,145,10120], + "enemy_1045_hammer": [1,0,1,145,8498], "enemy_1121_lifbos_2": [1,0,1,453,2161], - "enemy_1172_dugago": [1,0,1,573,2706], - "enemy_1195_sfyin": [36,25,11,1028,67596], - "enemy_1195_sfyin_2": [15,8,7,2711,37686], - "enemy_1196_msfyin": [50,40,10,0,83], - "enemy_1196_msfyin_2": [16,15,1,0,47], - "enemy_1197_sfshu": [9,7,2,506,21044], - "enemy_1198_msfshu": [21,13,8,0,46], - "enemy_1203_sfhu": [1,1,0,4133,7488], - "enemy_1204_msfhu": [4,0,4,0,5], - "enemy_1252_lysytb_2": [1,1,0,1377,10368], - "enemy_1305_mhslim": [1,0,1,608,268], - "enemy_1407_hummbd": [1,1,0,347,5184], - "enemy_1512_mcmstr": [1,0,1,7851,11737], - "enemy_2021_syfish": [1,0,1,145,3577] + "enemy_1172_dugago": [1,0,1,346,7726], + "enemy_1195_sfyin": [36,29,7,906,76246], + "enemy_1195_sfyin_2": [15,7,8,1605,39934], + "enemy_1196_msfyin": [58,44,14,0,99], + "enemy_1196_msfyin_2": [14,3,11,0,18], + "enemy_1197_sfshu": [9,6,3,468,21922], + "enemy_1198_msfshu": [18,10,8,0,39], + "enemy_1203_sfhu": [1,1,0,4335,7488], + "enemy_1204_msfhu": [4,0,4,0,4], + "enemy_1252_lysytb_2": [1,0,1,1937,5600], + "enemy_1305_mhslim": [1,1,0,232,1382], + "enemy_1407_hummbd": [1,1,0,77,5184], + "enemy_1512_mcmstr": [1,0,1,6828,7272], + "enemy_2021_syfish": [1,0,1,0,1177] }, - "hooks": [1,188,4200,679,679,939,964,449,126,176,30,30,0,18036,13,43,163,50,1,137,0,0,0], - "events": {"atk":791,"bounty":1,"deploy":23,"die":126,"dmg":950,"fx":127,"leak":50,"skill":60,"spawn":178,"status":56}, - "fx": {"aoe":2,"camouflage":1,"displace":20,"dp":3,"explode":20,"expose":1,"pull":20,"sp":1,"strike":3,"summon":42,"volley":7,"wolfShadow":2,"wolfShadowLost":5}, - "snaps": "1e865453 bf5ab273 da567691 fe6968a3 5a5519a4 176a1866 672c29cb e491de64 2d441cf0 124654c7 edb7abad 5b41e1b9 bcd0ec37 376197fb" + "hooks": [1,190,4200,653,653,915,920,449,116,179,30,29,0,19129,13,48,166,62,1,123,0,0,0], + "events": {"atk":805,"bounty":2,"deploy":22,"die":117,"dmg":909,"fx":142,"leak":62,"skill":59,"spawn":182,"status":53}, + "fx": {"aoe":2,"camouflage":1,"displace":28,"dp":2,"explode":19,"pull":28,"revive":1,"sp":1,"strike":3,"summon":44,"volley":7,"wolfShadow":2,"wolfShadowLost":4}, + "snaps": "1e865453 bf5ab273 da567691 fe6968a3 154137f2 3b5be2d3 2bf6358c 68ab8b27 047d4e31 50a382a4 359d8bbe a898fdd8 7f9a5c3c 08b0e4de" }, "roster-008": { "about": "pass 0 band 4: chess_char_3_06_b chess_char_3_12_b chess_char_3_02_b chess_char_3_03_b chess_char_3_04_b chess_char_3_05_b chess_char_3_21_a chess_char_3_01_b chess_char_3_08_b chess_char_3_09_b chess_char_3_10_b chess_char_3_11_b", @@ -542,23 +543,23 @@ "total": 171, "unspawned": 0, "bossHpLeft": null, - "rngDraws": 151, + "rngDraws": 164, "errors": {"count":0,"kinds":[]}, "players": {"p1":{"killed":171,"total":171,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":12,"dmg":279608,"bossDmg":0,"heal":534,"deaths":1,"layerGains":{}}}, "units": [ ["p1",1,"op","chess_char_3_16_b",1,"uniequip_002_snakek",5363,1,3,0,0,0,0,0,0], ["p1",2,"op","chess_char_4_01_a",2,null,4563.74,1,6,0,0,0,0,0,0], - ["p1",3,"op","chess_char_3_18_b",2,"uniequip_002_vulpis",3494.6,1,8,4,18685,534,534,22,2], - ["p1",4,"op","chess_char_4_04_a",1,null,1977.6,1,12,9,19246,1067,0,152,4], - ["p1",5,"op","chess_char_3_13_b",1,"uniequip_002_malist",2378.75,1,9,14,11985,0,0,67,31], - ["p1",6,"op","chess_char_3_14_b",1,"uniequip_002_slbell",2793.95,1,10,6,9477,0,0,49,3], - ["p1",7,"op","chess_char_3_17_b",1,"uniequip_002_shotst",3811.23,1,14,20,28968,1201,0,59,9], - ["p1",8,"op","chess_char_3_19_b",2,"uniequip_002_vigil",3110.54,1,4,2,5707,0,0,12,4], - ["p1",9,"op","chess_char_3_20_b",1,"uniequip_002_kjera",2508.5,1,7,3,1457,0,0,14,2], - ["p1",10,"op","chess_char_3_21_b",2,"uniequip_002_archet",2750.7,1,11,67,114797,0,0,211,5], + ["p1",3,"op","chess_char_3_18_b",2,"uniequip_002_vulpis",3494.6,1,8,4,19286,534,534,23,2], + ["p1",4,"op","chess_char_4_04_a",1,null,1977.6,1,12,9,19051,1067,0,144,4], + ["p1",5,"op","chess_char_3_13_b",1,"uniequip_002_malist",2378.75,1,9,17,12596,0,0,67,31], + ["p1",6,"op","chess_char_3_14_b",1,"uniequip_002_slbell",2793.95,1,10,8,9525,0,0,50,3], + ["p1",7,"op","chess_char_3_17_b",1,"uniequip_002_shotst",3811.23,1,14,20,29353,1201,0,58,9], + ["p1",8,"op","chess_char_3_19_b",2,"uniequip_002_vigil",3110.54,1,4,3,6258,0,0,17,4], + ["p1",9,"op","chess_char_3_20_b",1,"uniequip_002_kjera",2508.5,1,7,2,1598,0,0,21,2], + ["p1",10,"op","chess_char_3_21_b",2,"uniequip_002_archet",2750.7,1,11,66,112976,0,0,207,5], ["p1",11,"op","chess_char_4_02_a",2,null,2470.44,1,164,18,57817,2470,0,38,2], - ["p1",12,"op","chess_char_4_06_a",1,null,1479.3,1,13,27,10358,676,0,75,2], - ["p1",13,"token","token_10028_vigil_wolf",null,null,828.67,1,136,1,1113,4061,0,21,null], + ["p1",12,"op","chess_char_4_06_a",1,null,1338.48,1,13,23,10174,817,0,75,2], + ["p1",13,"token","token_10028_vigil_wolf",null,null,1026,1,163,1,974,3863,0,17,null], ["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null], ["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null] ], @@ -569,11 +570,11 @@ "enemy_1046_agent": [1,1,0,0,2700], "enemy_1148_dssbr_2": [1,1,0,0,3888], "enemy_1174_duholy": [1,1,0,1276,16200], - "enemy_1195_sfyin": [33,33,0,384,71280], + "enemy_1195_sfyin": [33,33,0,502,71280], "enemy_1196_msfyin": [66,66,0,0,132], "enemy_1197_sfshu": [12,12,0,315,32400], "enemy_1198_msfshu": [36,36,0,0,108], - "enemy_1199_sfjin": [6,6,0,175,42120], + "enemy_1199_sfjin": [6,6,0,0,42120], "enemy_1200_msfjin": [6,6,0,0,180], "enemy_1204_msfhu": [1,1,0,0,4], "enemy_1267_nhpbr": [1,1,0,750,4860], @@ -582,10 +583,10 @@ "enemy_1513_dekght_2": [1,1,0,6913,43200], "enemy_2034_sythef": [1,1,0,0,8640] }, - "hooks": [1,188,4746,743,743,1332,1316,115,173,173,64,62,0,21844,123,10,171,0,1,179,0,0,0], - "events": {"atk":918,"bounty":6,"deploy":15,"die":173,"dmg":1315,"fx":178,"skill":126,"spawn":186,"status":252}, - "fx": {"camouflage":2,"crit":20,"displace":33,"dp":5,"explode":3,"phase":1,"ripple":10,"shadowWeave":27,"shield":2,"shieldBreak":1,"stealth":4,"summon":51,"telegraph":3,"volley":9,"wolfShadow":3,"wolfShadowLost":3,"zone":1}, - "snaps": "666c3952 b094f1ea 38685430 9f090539 ee670c94 1074c7a5 93293922 a5617f28 5b7f9793 095e3e72 c46ed058 dbb905a8 02fb9aea d0e5cb5d a733910b 08ba7414" + "hooks": [1,188,4746,738,738,1313,1297,115,173,173,64,62,0,21844,132,9,171,0,1,179,0,0,0], + "events": {"atk":918,"bounty":6,"deploy":15,"die":173,"dmg":1296,"fx":184,"skill":126,"spawn":186,"status":261}, + "fx": {"camouflage":2,"crit":24,"displace":33,"dp":5,"explode":3,"phase":1,"ripple":10,"shadowWeave":27,"shield":2,"shieldBreak":1,"stealth":4,"summon":51,"telegraph":3,"volley":9,"wolfShadow":4,"wolfShadowLost":4,"zone":1}, + "snaps": "666c3952 b094f1ea 38685430 9f090539 ee670c94 1074c7a5 93293922 a5617f28 5b7f9793 3c0a7490 14387936 dcb74c1a a58e1beb 82ecbe91 64e48fa5 85eef6b4" }, "roster-010": { "about": "pass 0 band 4: chess_char_4_17_a chess_char_4_18_a chess_char_4_11_a chess_char_4_12_a chess_char_4_19_a chess_char_4_07_a chess_char_4_16_a chess_char_4_09_a chess_char_4_10_a chess_char_4_13_a chess_char_4_14_a chess_char_4_20_a token_10041_cathy_catsld token_10041_cathy_catsld", @@ -1694,8 +1695,8 @@ "enemy_9010_acpupp": [1,0,1,0,1540] }, "hooks": [1,98,4200,350,350,1399,1458,9,34,91,27,27,0,8299,26,36,73,56,1,38,0,0,0], - "events": {"atk":410,"bounty":3,"deploy":22,"die":35,"dmg":1457,"fx":19,"heal":9,"leak":56,"skill":54,"spawn":90,"status":52}, - "fx": {"dp":3,"explode":3,"palsy":1,"revive":4,"shield":1,"sp":2,"strike":1,"summon":2,"telegraph":2}, + "events": {"atk":410,"bounty":3,"deploy":22,"die":35,"dmg":1457,"fx":20,"heal":9,"leak":56,"skill":54,"spawn":90,"status":52}, + "fx": {"dp":3,"explode":3,"palsy":1,"revive":4,"shield":1,"sp":2,"strike":1,"summon":2,"telegraph":2,"wolfShadowLost":1}, "snaps": "b8ad25ab 03f51213 f84a4bf7 9a29353d 19f6d9a9 5330fd8a f6dda9d3 43873b05 57a77a2a 94494c3a ade46bcb e1b4f57e 436600a0 c60c5a58" }, "roster-027": { @@ -1769,39 +1770,39 @@ "bossHpLeft": null, "rngDraws": 84, "errors": {"count":0,"kinds":[]}, - "players": {"p1":{"killed":3,"total":62,"perfect":false,"leaks":59,"leaksUncounted":0,"coins":0,"dmg":157324,"bossDmg":0,"heal":105,"deaths":25,"layerGains":{}}}, + "players": {"p1":{"killed":3,"total":62,"perfect":false,"leaks":59,"leaksUncounted":0,"coins":0,"dmg":153754,"bossDmg":0,"heal":105,"deaths":26,"layerGains":{}}}, "units": [ ["p1",1,"op","chess_char_3_16_b",0,"none",0,0,4,0,70,4069,0,7,1], ["p1",2,"op","chess_char_4_01_a",0,null,0,0,7,0,0,3614,0,0,0], ["p1",3,"op","chess_char_3_18_b",0,"uniequip_003_vulpis",0,0,10,0,0,2743,105,0,0], - ["p1",4,"op","chess_char_4_04_a",0,null,799.54,1,90,0,15633,20901,0,35,5], + ["p1",4,"op","chess_char_4_04_a",0,null,0,0,91,0,13678,21701,0,34,4], ["p1",5,"op","chess_char_3_13_b",0,"none",0,0,1,0,15809,1802,0,35,1], - ["p1",6,"op","chess_char_3_14_b",0,"none",0,0,83,1,25147,4151,0,29,1], - ["p1",7,"op","chess_char_3_17_b",0,"none",0,0,89,1,31808,7884,0,21,7], - ["p1",8,"op","chess_char_3_19_b",0,"uniequip_003_vigil",0,0,2,1,24320,2429,0,34,3], + ["p1",6,"op","chess_char_3_14_b",0,"none",0,0,88,1,23224,4151,0,27,1], + ["p1",7,"op","chess_char_3_17_b",0,"none",0,0,82,1,31808,7884,0,21,7], + ["p1",8,"op","chess_char_3_19_b",0,"uniequip_003_vigil",0,0,2,1,24320,2429,0,34,4], ["p1",9,"op","chess_char_3_20_b",0,"none",0,0,9,0,4159,1897,0,11,1], - ["p1",10,"op","chess_char_3_21_b",0,"uniequip_003_archet",0,0,60,0,17805,4603,0,22,8], - ["p1",11,"op","chess_char_4_02_a",0,null,0,0,65,0,9310,5869,0,7,0], - ["p1",12,"op","chess_char_4_06_a",0,null,0,0,74,0,7987,3414,0,17,1], - ["p1",13,"token","token_10028_vigil_wolf",null,null,0,0,3,0,5276,3473,0,12,null] + ["p1",10,"op","chess_char_3_21_b",0,"uniequip_003_archet",0,0,61,0,17805,4603,0,22,8], + ["p1",11,"op","chess_char_4_02_a",0,null,0,0,66,0,9310,5869,0,7,0], + ["p1",12,"op","chess_char_4_06_a",0,null,0,0,74,0,8278,3414,0,17,1], + ["p1",13,"token","token_10028_vigil_wolf",null,null,0,0,39,0,5293,4499,0,13,null] ], "enemies": { "enemy_10044_wintun": [1,0,1,6689,14921], "enemy_10127_rkmbst_2": [1,0,1,1878,0], - "enemy_1019_jshoot_2": [18,0,18,8026,19151], - "enemy_1092_mdgint": [1,0,1,0,374], + "enemy_1019_jshoot_2": [18,0,18,7757,16859], + "enemy_1092_mdgint": [1,0,1,1026,391], "enemy_1174_duholy_2": [1,0,1,1445,892], "enemy_1234_dsubrl": [1,0,1,4496,214], "enemy_1328_cbjedi": [1,0,1,0,267], - "enemy_1381_winman": [21,3,18,7318,106180], - "enemy_1389_winbab_2": [15,0,12,34758,14615], + "enemy_1381_winman": [21,3,18,7653,104478], + "enemy_1389_winbab_2": [15,0,12,35491,15020], "enemy_1509_mousek": [1,0,1,1686,0], "enemy_9006_actoxi": [1,0,1,554,712] }, - "hooks": [1,89,5100,404,404,513,529,30,29,85,28,28,0,4321,18,29,62,56,1,32,0,0,18], - "events": {"atk":431,"deploy":27,"die":29,"dmg":505,"fx":84,"leak":56,"skill":56,"spawn":75,"status":83}, - "fx": {"camouflage":1,"crit":6,"dodge":18,"dp":3,"phase":1,"pulse":17,"sentry":8,"shadowWeave":11,"shield":4,"shieldBreak":2,"summon":2,"telegraph":1,"volley":6,"wolfShadowLost":3,"zone":1}, - "snaps": "7c092213 c094fdd0 61afa7ba 7ab796f5 181fca6b 71abad47 8bc283c6 0928fca3 2eb15bdf 35788dd1 8be98855 61cc5888 771b79b1 2212bef6 d09f87b0 fc2cc2ef d14c6547" + "hooks": [1,90,5100,401,401,504,520,30,31,87,28,28,0,4426,16,27,62,56,1,34,0,0,18], + "events": {"atk":426,"deploy":28,"die":31,"dmg":496,"fx":88,"leak":56,"skill":56,"spawn":75,"status":79}, + "fx": {"camouflage":1,"crit":6,"dodge":18,"dp":4,"phase":1,"pulse":17,"revive":1,"sentry":9,"shadowWeave":9,"shield":4,"shieldBreak":2,"summon":3,"telegraph":1,"volley":6,"wolfShadowLost":5,"zone":1}, + "snaps": "7c092213 c094fdd0 61afa7ba 7ab796f5 513e5d4e 4a180eed f5d75fff 20004d5b d4cf3e4e e860333f 2580eac0 7b4a044a 27c913a4 dd5637ac f12e3851 a9e14dc9 76318156" }, "roster-029": { "about": "pass 1 band 4: chess_char_4_17_a chess_char_4_18_a chess_char_4_11_a chess_char_4_12_a chess_char_4_19_a chess_char_4_07_a chess_char_4_16_a chess_char_4_09_a chess_char_4_10_a chess_char_4_13_a chess_char_4_14_a chess_char_4_20_a token_10041_cathy_catsld token_10041_cathy_catsld", @@ -2510,8 +2511,8 @@ "enemy_9016_acstmr": [1,1,0,0,8] }, "hooks": [1,95,5100,533,533,815,851,518,50,90,17,28,16,7820,316,21,65,40,1,60,0,1,0], - "events": {"atk":606,"bounty":1,"deploy":28,"die":50,"dmg":845,"fx":93,"leak":40,"skill":34,"spawn":80,"status":171}, - "fx": {"beam":5,"block":3,"buff":2,"burst":1,"coldWind":1,"displace":15,"dp":1,"iceSpike":1,"liberate":1,"overclock":1,"overload":1,"palsy":3,"revive":3,"rockfall":1,"sentry":6,"shadowWeave":11,"shield":7,"shieldBreak":6,"stone":1,"swordStorm":7,"telegraph":2,"volley":9,"wake":1,"wolfShadow":1,"wolfShadowLost":2,"zone":1}, + "events": {"atk":606,"bounty":1,"deploy":28,"die":50,"dmg":845,"fx":94,"leak":40,"skill":34,"spawn":80,"status":171}, + "fx": {"beam":5,"block":3,"buff":2,"burst":1,"coldWind":1,"displace":15,"dp":1,"iceSpike":1,"liberate":1,"overclock":1,"overload":1,"palsy":3,"revive":3,"rockfall":1,"sentry":6,"shadowWeave":11,"shield":7,"shieldBreak":6,"stone":1,"swordStorm":7,"telegraph":2,"volley":9,"wake":1,"wolfShadow":1,"wolfShadowLost":3,"zone":1}, "snaps": "de3ca31c 182b136c 5c27b035 f8f15ac1 250e93d3 6fde9171 1273cfdd f1c0ebf4 8e50442c 8277b17d 3a6591ca 5a144122 2014c207 a4445fd0 3d27fbdc 8d62308c d9b05d02" }, "roster-041": {