feedback7: merge fb7-gh-sim — PR #348 (take-the-highest chains), PR #293 (REA-Y in the engine), #329 ports (异客 13 tiles, 歌蕾蒂娅 net radius 1, 搅拌机 latest first, 大帝 uncapped), PR #365 (骨刺 forms + PRTS targeting), #298 (instant non-attack skills recast at most once per attack interval, attacks untouched), #364 (先兆者 shell 3×3), #220 (速射手 air first), 余 skips noStack bonds (owner)
- Conflict: docs/PLAYING.md 技能自动释放 bullet — both insertions kept (the 铁钳号 rule from fb7-owner-sim, the #298
pacing sentence).
- Grok review: MERGE AFTER FIXES — the HIGH #298 finding (the cast replaced the due attack) fixed on the branch
(db29acf9: per-skill nextCastAt; golden of #298 alone shrank from 84 scenarios to 2).
- Golden union: 4 matches differ from HEAD though the branch alone left them (coop2-NORMAL-3, coop4-ABYSS-7,
coop2-NORMAL-8-serverrun, solo-HARD-9-boosted): HEAD's side had moved all four (#333's shop RNG …) and on those new
bot paths the merged rules apply — the diffs are in-battle layer gains / damage (卡西米尔 50 → 45, 精准 80 → 76,
叙拉古 176 → 168, kills ±2, solo-HARD-9 LP 397 → 399), no outcome change.
- Broad node --test 5571 (5570 pass, 0 fail, 1 skipped); lint 0 errors; typecheck.
This commit is contained in:
commit
52f56319ac
41 files changed
+2065
-648
No files matched your search
@@ -7,7 +7,6 @@ import {
|
||||
AURA, num, tbb, grid, nationOf, enemiesOnRange, targetsInRange, whileDeployed, reveal, skillActive, isSel, alt,
|
||||
instantKind, withDefaults,
|
||||
} from '../shared/tier4.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
/** Is module `id` the active one? (elites; `'none'` / normal chess ⇒ false) */
|
||||
const moduleIs = (def, id) => !!(def?.raw?.module?.active && def.raw.module.id === id);
|
||||
@@ -31,7 +30,9 @@ export default withDefaults({
|
||||
return {
|
||||
skills: alt(def, {
|
||||
// 铳骑主考官: the next attack is a triple hit of atk_scale each and reloads `charge` bullet(s) into ONE other
|
||||
// 拉特兰 operator around him (8 tiles, [ASSUMED] nearest first) whose ammo skill runs
|
||||
// 拉特兰 operator around him whose ammo skill runs — PRTS 备注 「补弹范围为周围8格,仅选择可补弹的干员为目标,优先选择最后
|
||||
// 部署的干员」: the latest deployment first (battle-wide deploySeq: a redeploy is a new deployment). Until 0.2.2 the
|
||||
// nearest first [ASSUMED] (GitHub #325, PR #329)
|
||||
skchr_rmixer_1: () => ({
|
||||
kind: instantKind(def),
|
||||
attack: {
|
||||
@@ -40,7 +41,7 @@ export default withDefaults({
|
||||
const n = num(bb.charge, 1);
|
||||
const cand = battle.alliesInRadius(unit.x, unit.y, 1.5, unit.ownerId)
|
||||
.filter((a) => a !== unit && a.kind === 'op' && nationOf(a) === 'laterano' && a.skill && a.skill.active && a.skill.kind === 'ammo')
|
||||
.sort((a, b) => hypot(a.x - unit.x, a.y - unit.y) - hypot(b.x - unit.x, b.y - unit.y) || a.id - b.id);
|
||||
.sort((a, b) => b.deploySeq - a.deploySeq || a.id - b.id);
|
||||
if (!cand[0] || !(n > 0)) return;
|
||||
cand[0].skill.addAmmo(n);
|
||||
battle.fx('reload', { x: cand[0].x, y: cand[0].y, id: cand[0].id, n });
|
||||
|
||||
@@ -12,14 +12,17 @@ const ABYSSAL = new Set(['char_143_ghost', 'char_263_skadi', 'char_474_glady', '
|
||||
const isAbyssal = (u) => u?.def?.raw?.groupId === 'abyssal' || ABYSSAL.has(u?.def?.charId ?? u?.def?.raw?.charId);
|
||||
|
||||
export default withDefaults({
|
||||
// ===== 歌蕾蒂娅 (hookmaster) S3 缺水的碎漩狂舞 — bind a far target, tornado: slow, 85 % arts pulses + pull, final pull
|
||||
// ===== 歌蕾蒂娅 (hookmaster) S3 缺水的碎漩狂舞 — bind a far target, tornado (r 1.5): slow, 85 % arts pulses + pull,
|
||||
// final pull (the 捕网, r 1)
|
||||
// S1 缺水的大洋裂断 (charges: next attack pulls the target to her front, 150 %/180 %); S2 缺水的掌握怒海 (BAT +0.5 s,
|
||||
// wider range, ≤ 2 targets — blocked first — at 135 %/150 % and pulled to her front); module HOK-Y (淡金坠饰):
|
||||
// a pull towards herself of an enemy farther than 2.5 tiles is one force level stronger
|
||||
chess_char_4_12_a: (bb, chess, def) => {
|
||||
const t0 = tbb(def, 0), t1 = tbb(def, 1), mb = moduleBb(def);
|
||||
const tb = def.traitBb || {};
|
||||
const R = 1.5; // [ASSUMED] tornado radius (no blackboard key)
|
||||
// S3 radii (no blackboard key) — PRTS 备注: 「龙卷风半径1.5」 (slow, pulses) and 「技能结束时的拖拽为捕网,捕网半径为1,无伤害」
|
||||
// (the skill-end pull; until 0.2.2 it reused the tornado's 1.5 — GitHub #324, PR #329)
|
||||
const R = 1.5, NET_R = 1;
|
||||
const force = num(bb.force, num(bb['attack@force'], 0));
|
||||
const iv = Math.max(0.1, num(bb.interval, 1.5));
|
||||
const g = grid(def.skill?.rangeGrid);
|
||||
@@ -76,7 +79,7 @@ export default withDefaults({
|
||||
const T = unit.mem.tornado;
|
||||
unit.mem.tornado = null;
|
||||
if (!T || reason === 'death' || !unit.alive) return;
|
||||
for (const e of battle.foesInRadius(T.x, T.y, R)) pullSelf(battle, unit, e, force);
|
||||
for (const e of battle.foesInRadius(T.x, T.y, NET_R)) pullSelf(battle, unit, e, force); // the 捕网
|
||||
battle.fx('pull', { x: T.x, y: T.y, id: unit.id });
|
||||
},
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Conventions of the tier-5 kits: ../shared/tier5.js; kit contract and rules: ../README.md.
|
||||
|
||||
import {
|
||||
num, on, talent, batPct, mods, inFaction, isOp, selectedId, lazySkills, skillRange, crowdAspd,
|
||||
num, on, talent, batPct, mods, inFaction, isOp, selectedId, lazySkills, skillRange,
|
||||
} from '../shared/tier5.js';
|
||||
|
||||
const isLaterano = (u) => inFaction(u, 'lateranoShip', ['laterano']);
|
||||
@@ -100,7 +100,9 @@ export default {
|
||||
} },
|
||||
],
|
||||
install(battle, unit) {
|
||||
crowdAspd(battle, unit, 'excu2:module', num(tm.attack_speed), num(tm['trigger_cnt[equip]']));
|
||||
// REA-Y "攻击范围内存在2名及以上敌人时攻击速度+12": the ENGINE owns this trait line now
|
||||
// (server/sim/content/traitMods.js, applied from battle/players.js _setupUnit for every operator) — a second
|
||||
// implementation here would count the same 12 points twice.
|
||||
if (sid === 'skchr_excu2_3') { // S3: +attack@atk ATK per bullet spent
|
||||
battle.on('ammoUsed', (c) => {
|
||||
if (c.unit !== unit || !unit.skill?.active) return;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import { mitigate } from '../../../damage.js';
|
||||
import {
|
||||
RING1, num, on, talent, talentRec, traitBb, skillGrid, mods, dist, lazySkills, instantKind, crowdAspd,
|
||||
RING1, num, on, talent, talentRec, skillGrid, mods, dist, lazySkills, instantKind,
|
||||
} from '../shared/tier5.js';
|
||||
|
||||
/** 隐德来希 S3 "每次攻击对心烛至少造成35%攻击力的伤害". */
|
||||
@@ -23,7 +23,7 @@ export default {
|
||||
// for atk_scale × ATK phys every `interval` s — ground enemies only unless the carrier has taken off (PRTS 备注).
|
||||
// Module REA-Y (elite): ASPD +12 with ≥ 2 enemies in range.
|
||||
chess_char_5_06_a: (bb, chess, def) => {
|
||||
const t0 = talent(chess, 0), t1 = talent(chess, 1), tb = traitBb(chess);
|
||||
const t0 = talent(chess, 0), t1 = talent(chess, 1);
|
||||
const hpScale = num(bb['attack@max_hp_scale'], 0.6), defScale = num(bb['attack@def_scale'], 1), resScale = num(bb['attack@magic_resistance_scale'], 1);
|
||||
const nCandles = Math.max(1, num(bb['attack@max_target'], 3));
|
||||
const candleKey = talentRec(chess, 2)?.bbStr?.take_extra_enemy_key ?? CANDLE_KEY;
|
||||
@@ -140,7 +140,9 @@ export default {
|
||||
} },
|
||||
],
|
||||
install(battle, unit) {
|
||||
crowdAspd(battle, unit, 'etlchi:module', num(tb.attack_speed), num(tb.cnt));
|
||||
// REA-Y "攻击范围内存在2名及以上敌人时攻击速度+12": the ENGINE owns this trait line now
|
||||
// (server/sim/content/traitMods.js, applied from battle/players.js _setupUnit for every operator) — a second
|
||||
// implementation here would count the same 12 points twice.
|
||||
const mine = (e) => e && e.mem && e.mem.candleOwner === unit;
|
||||
// "心烛只受隐德来希攻击的影响": other sources' element gauges never fill a candle either
|
||||
battle.on('elementHit', (c) => { if (mine(c.target) && c.source !== unit) c.dmg.cancel = true; }, { owner: unit });
|
||||
|
||||
@@ -10,7 +10,12 @@ import { powi } from '../../../detmath.js';
|
||||
// ------------------------------------------------------------------------------------------------------------------
|
||||
// 异客 chess_char_6_05 (链术师) — S3 辉煌裂片; 机理分析; 孤卒
|
||||
|
||||
const STORM_RADIUS = 1.5; // [ASSUMED] storm zone radius (not in data)
|
||||
/** S3 雷暴区域: PRTS 备注 「以其判定中心所在格为中心生成一个x-1的雷暴区域」 — range x-1 (range_table: the diamond of radius 2,
|
||||
* 13 tiles) around the tile of the target's centre (GitHub #322, PR #329). Until 0.2.2 a 1.5-tile circle around the
|
||||
* target's exact position [ASSUMED]. The chain of each strike bounces on its own radius, inside or outside the zone. */
|
||||
const STORM_GRID = Object.freeze([[2, 0], [1, -1], [1, 0], [1, 1], [0, -2], [0, -1], [0, 0], [0, 1], [0, 2], [-1, -1], [-1, 0], [-1, 1], [-2, 0]]);
|
||||
/** The storm fx's ring radius (tiles): the diamond's reach — drawn only, the zone is STORM_GRID. */
|
||||
const STORM_FX_RADIUS = 2;
|
||||
|
||||
function pasngr(bb, chess, def) {
|
||||
const t0 = tbb(def, 0), t1 = tbb(def, 1), tb = def?.traitBb || {};
|
||||
@@ -69,16 +74,18 @@ function pasngr(bb, chess, def) {
|
||||
const cands = enemiesIn(battle, unit, keys);
|
||||
if (!cands.length) return;
|
||||
const tgt = cands.reduce((a, b) => (b.hp > a.hp ? b : a));
|
||||
const cx = tgt.x, cy = tgt.y;
|
||||
// 判定中心所在格: the tile of the target's centre (the rounding of body.js posKey); fixed for the storm's 4 s
|
||||
const cx = Math.round(tgt.x), cy = Math.round(tgt.y);
|
||||
const zoneKeys = absoluteRangeKeys(STORM_GRID, cy, cx, 1, 0);
|
||||
const dur = num(bb.duration, 4), iv = Math.max(0.1, num(bb.interval, 0.5)), scale = num(bb.atk_scale, 1);
|
||||
const n = Math.max(1, Math.round(dur / iv));
|
||||
battle.fx('storm', { x: cx, y: cy, id: unit.id, duration: dur, r: STORM_RADIUS });
|
||||
battle.fx('storm', { x: cx, y: cy, id: unit.id, duration: dur, r: STORM_FX_RADIUS });
|
||||
let k = 0;
|
||||
const seq = unit.deploySeq;
|
||||
const h = battle.every(iv, () => {
|
||||
if (!live(unit) || unit.deploySeq !== seq) { h.cancel(); return; } // the storm ends when she leaves the field
|
||||
if (++k >= n) h.cancel();
|
||||
const zone = battle.foesInRadius(cx, cy, STORM_RADIUS).filter((e) => canTargetEnemy(unit, e, ANY));
|
||||
const zone = enemiesIn(battle, unit, zoneKeys); // every enemy she can select whose body is on the 13 tiles
|
||||
const e = battle.rng.pick(zone);
|
||||
if (e) strike(battle, unit, e, scale);
|
||||
}, { owner: unit });
|
||||
|
||||
Reference in new issue
Block a user