feedback7: the battle sim uses engine-independent math — Chrome, Firefox, Safari and the server agree bit for bit (PR #373 by @Cloudnyco)
- server/sim/detmath.js: hypot (sqrt of the squares, rescaled outside 2^±500), powi (integer powers in double-double, one rounding), sin / cos (fdlibm 5.3) and atan2 (fdlibm / FreeBSD msun) built only from correctly rounded IEEE operations; the sim's Math.hypot / sin / cos / atan2 / pow calls go through it (65 files); eslint.config.js forbids the approximated Math functions under server/sim and test/sim/detmath.test.js scans for them. - sin / cos / atan2 are the algorithms V8 runs in Node, so the server's results stay; only hypot differs from V8's. Golden: 2 moved — diy-027 (a hypot distance, damage +80) and coop3-ABYSS-5 (a later card pick from round 9, same end). - Applied unchanged after review (Grok static review: MERGE; the diff read in full: no network, no new dependency). - Tests: broad node --test 5155 / 5155 with the new file tracked (detmath 7 / 7, package 10 / 10); lint 0 errors, typecheck, check:imports.
This commit is contained in:
1 parent
2580152a3a
commit
ae22335cfe
65 files changed
+616
-141
No files matched your search
@@ -2,6 +2,7 @@
|
||||
// Conventions of the tier-2 kits: ../shared/tier2.js; kit contract and rules: ../README.md.
|
||||
|
||||
import { num, talentBb, traitBb, onHitBy, alliesInGridOf, toggleBuff } from '../shared/tier1.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
export default {
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
@@ -33,7 +34,7 @@ export default {
|
||||
const mn = num(tb.min_dist), mx = num(tb.max_dist, 4), ds = num(tb.damage_scale);
|
||||
onHitBy(battle, unit, ({ target, dmg }) => {
|
||||
if (!dmg.isAttack) return;
|
||||
const d = Math.hypot(target.x - unit.x, target.y - unit.y);
|
||||
const d = hypot(target.x - unit.x, target.y - unit.y);
|
||||
dmg.amount *= 1 + ds * (mx > mn ? Math.max(0, Math.min(1, (d - mn) / (mx - mn))) : 1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
num, defOf, talentBb, moduleTalentBb, selectedId, altSkills, alive, gridKeys, fx, copyGrid, textNum, freeTile,
|
||||
groundTile, enemiesOn,
|
||||
} from '../shared/tier3.js';
|
||||
import { powi } from '../../../detmath.js';
|
||||
|
||||
/** 琳琅诗怀雅 S3's coin range (PRTS 备注 "前方范围2-4"; range_table "2-4", facing right). */
|
||||
const SWIRE2_COIN_GRID = Object.freeze([[1, 1], [0, 0], [0, 1], [0, 2], [-1, 1]]);
|
||||
@@ -267,7 +268,7 @@ export default {
|
||||
battle.on('fatal', (ctx) => {
|
||||
if (ctx.unit !== unit || ctx.prevented) return;
|
||||
const n = unit.mem.saveCount ?? 0;
|
||||
const cost = Math.abs(num(t1.cost, -5)) * Math.pow(num(t1.cost_multi, 2), n);
|
||||
const cost = Math.abs(num(t1.cost, -5)) * powi(num(t1.cost_multi, 2), n);
|
||||
const pl = battle.getPlayer(unit.ownerId);
|
||||
if (!pl || pl.dp + 1e-9 < cost) return;
|
||||
battle.addDp(unit.ownerId, -cost);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import { PUSH_DIRECTIONAL_MIN_DIST } from '../../../constants.js';
|
||||
import { num, defOf, talentBb, traitBb, onTiles, gridKeys, fx, copyGrid } from '../shared/tier3.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
/** Two enemy bodies touch within this distance (tiles) — 见行者 collision stun. */
|
||||
const COLLIDE = 0.6;
|
||||
@@ -33,7 +34,7 @@ export default {
|
||||
// (only the angle: a target nearer than 0.25 tile still turns radial at 受力等级 −2) — by the official
|
||||
// 力度 − 重量 distance of a 特效 push (PRTS 推与拉 names 见行者's skills 特效类; Battle.push / pushDistance);
|
||||
// stopped short of it ⇒ it hit a wall
|
||||
const near = Math.hypot(e.x - unit.x, e.y - unit.y) < PUSH_DIRECTIONAL_MIN_DIST;
|
||||
const near = hypot(e.x - unit.x, e.y - unit.y) < PUSH_DIRECTIONAL_MIN_DIST;
|
||||
const expect = battle.pushDistance(e, near ? force - 2 : force, { effect: true });
|
||||
const moved = battle.push(e, force, { from: unit, dir: { x: unit.fwd[1], y: unit.fwd[0] }, fixedAngle: true, effect: true });
|
||||
const wall = expect > 0 && moved + 0.05 < expect;
|
||||
|
||||
@@ -7,6 +7,7 @@ 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);
|
||||
@@ -39,7 +40,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) => Math.hypot(a.x - unit.x, a.y - unit.y) - Math.hypot(b.x - unit.x, b.y - unit.y) || a.id - b.id);
|
||||
.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);
|
||||
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 });
|
||||
|
||||
@@ -49,7 +49,7 @@ export default withDefaults({
|
||||
unit.mem.sentry = null;
|
||||
unit.mem.inesSentryAt = null;
|
||||
const ax = from.x, ay = from.y, bx = unit.x, by = unit.y;
|
||||
const L2 = (bx - ax) ** 2 + (by - ay) ** 2;
|
||||
const L2 = (bx - ax) * (bx - ax) + (by - ay) * (by - ay);
|
||||
const w = num(bb.projectile_range, 1.4);
|
||||
const hits = [];
|
||||
for (const e of battle.aliveEnemies()) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { COLS } from '../../../constants.js';
|
||||
import {
|
||||
num, tbb, grid, targetsInRange, targetsInGrid, toggleBuff, skillActive, withDefaults,
|
||||
} from '../shared/tier4.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
/**
|
||||
* Free tile for a summon/device: in the rect, standable, not reserved (Battle.isReservedTile: empty, no knocked-out
|
||||
@@ -39,7 +40,7 @@ export default withDefaults({
|
||||
for (const k of unit.rangeKeys || []) {
|
||||
const r = (k / COLS) | 0, c = k % COLS;
|
||||
if (!freeTile(battle, r, c)) continue;
|
||||
const d = Math.hypot(c - ref.x, r - ref.y) + (battle.grid.groundPassable(r, c) ? 0 : 5);
|
||||
const d = hypot(c - ref.x, r - ref.y) + (battle.grid.groundPassable(r, c) ? 0 : 5);
|
||||
if (d < bd - 1e-9) { bd = d; best = [r, c]; }
|
||||
}
|
||||
if (!best) return;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import { bodyInKeys } from '../../../body.js';
|
||||
import { num, tbb, keySet, skillActive, isSel, alt, instantKind, withDefaults } from '../shared/tier4.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
/** 阿罗玛's 非首次标记 (talent 起泡性能测试): one per enemy, shared by every 阿罗玛 (PRTS 备注). */
|
||||
const AROMA_MARK = 'aroma:bubbled';
|
||||
@@ -71,7 +72,7 @@ export default withDefaults({
|
||||
const lo = num(tb.min_dist, 0), hi = num(tb.max_dist, 4);
|
||||
battle.on('hit', (c) => {
|
||||
if (c.source !== unit || !c.dmg.isAttack) return;
|
||||
const d = Math.hypot(c.target.x - unit.x, c.target.y - unit.y);
|
||||
const d = hypot(c.target.x - unit.x, c.target.y - unit.y);
|
||||
c.dmg.mul *= 1 + ds * Math.max(0, Math.min(1, (d - lo) / Math.max(1e-6, hi - lo)));
|
||||
}, { owner: unit });
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
AURA, num, tbb, moduleBb, grid, batFlat, enemyHasTag, targetsInRange, whileDeployed, pulse, alt, instantKind,
|
||||
pullToFront, withDefaults,
|
||||
} from '../shared/tier4.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
const ABYSSAL = new Set(['char_143_ghost', 'char_263_skadi', 'char_474_glady', 'char_4145_ulpia', 'char_1023_ghost2']);
|
||||
const isAbyssal = (u) => u?.def?.raw?.groupId === 'abyssal' || ABYSSAL.has(u?.def?.charId ?? u?.def?.raw?.charId);
|
||||
@@ -28,7 +29,7 @@ export default withDefaults({
|
||||
};
|
||||
// module HOK-Y: "向自身拖拽较远的敌人时力度提升一个等级"
|
||||
const farR = num(mb['skill@range_radius'], num(mb['attack@range_radius'], 0)), farF = num(mb['skill@delta_force'], num(mb['attack@delta_force'], 0));
|
||||
const selfForce = (unit, e, f) => (farR > 0 && farF > 0 && Math.hypot(e.x - unit.x, e.y - unit.y) > farR + 1e-9 ? f + farF : f);
|
||||
const selfForce = (unit, e, f) => (farR > 0 && farF > 0 && hypot(e.x - unit.x, e.y - unit.y) > farR + 1e-9 ? f + farF : f);
|
||||
const pullSelf = (battle, unit, e, f) => { if (e && e.alive) dragDmg(battle, unit, e, pullToFront(battle, unit, e, selfForce(unit, e, f))); };
|
||||
return {
|
||||
skills: alt(def, {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { bodyInKeys } from '../../../body.js';
|
||||
import { absoluteRangeKeys, sortEnemyTargets } from '../../../targeting.js';
|
||||
import { isHpLoss } from '../../../damage.js';
|
||||
import { num, tbb, whileDeployed, toggleBuff, skillActive, isSel, alt, withDefaults } from '../shared/tier4.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
const LINE = Object.freeze(Array.from({ length: COLS }, (_, i) => Object.freeze([0, i])));
|
||||
const tileKey = (u) => Math.round(u.y) * COLS + Math.round(u.x);
|
||||
@@ -83,7 +84,7 @@ export default withDefaults({
|
||||
battle.on('hit', (c) => {
|
||||
if (c.source !== unit || !c.dmg.isAttack) return;
|
||||
if (S3 && skillActive(unit) && unit.baseRangeKeys && !bodyInKeys(c.target, unit.baseRangeKeys)) c.dmg.mul *= num(bb.damage_scale, 1.25);
|
||||
if (ds > 0) { const d = Math.hypot(c.target.x - unit.x, c.target.y - unit.y); c.dmg.mul *= 1 + ds * Math.max(0, Math.min(1, (d - lo) / Math.max(1e-6, hi - lo))); }
|
||||
if (ds > 0) { const d = hypot(c.target.x - unit.x, c.target.y - unit.y); c.dmg.mul *= 1 + ds * Math.max(0, Math.min(1, (d - lo) / Math.max(1e-6, hi - lo))); }
|
||||
}, { owner: unit });
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { COLS } from '../../../constants.js';
|
||||
import { rotateOffset } from '../../../dir.js';
|
||||
import { AURA_IV, AURA_DUR, num, talent, traitBb, skillGrid, isOp, leaderOf, whileOn } from '../shared/tier5.js';
|
||||
import { hypot, sin, cos } from '../../../detmath.js';
|
||||
|
||||
/** 魔王 T1 微尘: collision radius of one mote (PRTS 备注 0.4); orbit radius / speed come from the talent bb. */
|
||||
const MOTE_HIT_RADIUS = 0.4;
|
||||
@@ -82,11 +83,11 @@ export default {
|
||||
const ang = spd * t + (2 * Math.PI * k) / cnt;
|
||||
// the orbit turns with his direction: angle 0 = straight ahead, π/2 = his left hand
|
||||
const [fr, fc] = unit.fwd, [lr, lc] = rotateOffset(1, 0, unit.dir);
|
||||
const mx = unit.x + orbit * (fc * Math.cos(ang) + lc * Math.sin(ang)), my = unit.y + orbit * (fr * Math.cos(ang) + lr * Math.sin(ang));
|
||||
const mx = unit.x + orbit * (fc * cos(ang) + lc * sin(ang)), my = unit.y + orbit * (fr * cos(ang) + lr * sin(ang));
|
||||
let hit = null, hd = Infinity;
|
||||
for (const a of cands) {
|
||||
if (a.findBuff('cetsyr:mote')) continue;
|
||||
const d = Math.hypot(a.x - mx, a.y - my);
|
||||
const d = hypot(a.x - mx, a.y - my);
|
||||
if (d <= MOTE_HIT_RADIUS + 1e-9 && d < hd) { hd = d; hit = a; }
|
||||
}
|
||||
if (!hit) continue;
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
AURA_IV, AURA_DUR, RING1, num, on, talent, skillGrid, maxCharges, mods, dist, isOp, selectedId, lazySkills,
|
||||
instantKind, whileOn, permBuff,
|
||||
} from '../shared/tier5.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
/** Straight-road length through each tile (max of the horizontal and vertical runs of ground-passable tiles). */
|
||||
const ROAD_CACHE = new WeakMap();
|
||||
@@ -134,7 +135,7 @@ export default {
|
||||
}
|
||||
const extra = battle.alliesInGrid(unit).some((a) => a !== unit && isOp(a)) ? Math.min(num(t0.projectile_extend), num(t0.projectile_extend_max, Infinity)) : 0;
|
||||
// it drifts away from her deployment tile ("移动方向始终为远离棘刺部署位置中心的方向")
|
||||
const dx = t.x - unit.x, dy = t.y - unit.y, len = Math.hypot(dx, dy) || 1;
|
||||
const dx = t.x - unit.x, dy = t.y - unit.y, len = hypot(dx, dy) || 1;
|
||||
// `key`: the fx re-sent every second as the unit drifts and grows is this one zone — the client updates it in
|
||||
// place instead of stacking a new layer each second (render/fx/zones.js; community report of 2026-10-06)
|
||||
const key = `thorn2:${unit.id}:${unit.mem.zoneSeq = (unit.mem.zoneSeq ?? 0) + 1}`;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { COLS } from '../../../constants.js';
|
||||
import { bodyInKeys } from '../../../body.js';
|
||||
import { num, tbb, moduleBb, live, keyOf, enemiesIn, isTok, instantKind, AROUND8, aura } from '../shared/tier6.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
// ------------------------------------------------------------------------------------------------------------------
|
||||
// 圣聆初雪 chess_char_6_02 (阵法术师) — S3 群山俯首; 无垠的雪景; 圣山的祝福; module 千分之一的心
|
||||
@@ -217,7 +218,7 @@ function lureGoal(battle, unit, e) {
|
||||
for (const [dr, dc] of AROUND8) {
|
||||
const r = unit.tileR + dr, c = unit.tileC + dc;
|
||||
if (!battle.grid.groundPassable(r, c)) continue;
|
||||
const d = Math.hypot(e.x - c, e.y - r);
|
||||
const d = hypot(e.x - c, e.y - r);
|
||||
if (d < bd - 1e-9) { bd = d; goal = [r, c]; }
|
||||
}
|
||||
return goal;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { absoluteRangeKeys, canTargetEnemy } from '../../../targeting.js';
|
||||
import { CHAIN_RADIUS } from '../../../constants.js';
|
||||
import { bodyDist, bodyInKeys } from '../../../body.js';
|
||||
import { num, bv, tbb, live, ANY, enemiesIn, batOf, N4, aura } from '../shared/tier6.js';
|
||||
import { powi } from '../../../detmath.js';
|
||||
|
||||
// ------------------------------------------------------------------------------------------------------------------
|
||||
// 异客 chess_char_6_05 (链术师) — S3 辉煌裂片; 机理分析; 孤卒
|
||||
@@ -25,7 +26,7 @@ function pasngr(bb, chess, def) {
|
||||
for (let i = 0; i < count && prev; i++) {
|
||||
hit.add(prev.id);
|
||||
battle.fx('lightning', { x: prev.x, y: prev.y, id: prev.id, src: unit.id });
|
||||
battle.dealDamage(unit, prev, { amount: unit.s.atk * scale * Math.pow(1 - num(ch.falloff, 0.15), i), type: 'arts', isSkill: true, isAttack: true, tags: ['skill', 'storm'] });
|
||||
battle.dealDamage(unit, prev, { amount: unit.s.atk * scale * powi(1 - num(ch.falloff, 0.15), i), type: 'arts', isSkill: true, isAttack: true, tags: ['skill', 'storm'] });
|
||||
if (slug > 0 && prev.alive) battle.applyStatus(prev, 'sluggish', { duration: slug, source: unit });
|
||||
let best = null, bd = Infinity;
|
||||
for (const x of battle.foesInRadius(prev.x, prev.y, ch.radius || CHAIN_RADIUS)) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { sortEnemyTargets, canTargetEnemy } from '../../../targeting.js';
|
||||
import { rotateOffset } from '../../../dir.js';
|
||||
import { bodyDist } from '../../../body.js';
|
||||
import { num, tbb, hasBond, ANY, selectedSkill, skillGridOf, WHOLE_FIELD, bstate, aura } from '../shared/tier6.js';
|
||||
import { hypot, sin, cos, atan2 } from '../../../detmath.js';
|
||||
|
||||
// ------------------------------------------------------------------------------------------------------------------
|
||||
// 荒芜拉普兰德 chess_char_6_18 (驭械术师) — S3 终幕·浩劫; 头狼; 叙拉古的荣幸
|
||||
@@ -39,10 +40,10 @@ function whitw2(bb, chess, def) {
|
||||
// ① `k` drones leave her evenly spread, the first along her facing (row 0 is the bottom row: angles in the (col, row)
|
||||
// plane, counter-clockwise)
|
||||
const releaseDrones = (unit, k) => {
|
||||
const a0 = Math.atan2(unit.fwd[0], unit.fwd[1]);
|
||||
const a0 = atan2(unit.fwd[0], unit.fwd[1]);
|
||||
for (let i = 0; i < k; i++) {
|
||||
const a = a0 + (2 * Math.PI * i) / k;
|
||||
unit.mem.drones.push({ x: unit.x, y: unit.y, hx: Math.cos(a), hy: Math.sin(a), v: WHITW2_SPREAD.v0, age: 0, phase: 'spread', t: null, cd: 0, rampId: null, ramp: 0, orbit: null });
|
||||
unit.mem.drones.push({ x: unit.x, y: unit.y, hx: cos(a), hy: sin(a), v: WHITW2_SPREAD.v0, age: 0, phase: 'spread', t: null, cd: 0, rampId: null, ramp: 0, orbit: null });
|
||||
}
|
||||
};
|
||||
// speed v → v + acc·dt (capped); the distance covered at the mean of the two (exact under constant acceleration)
|
||||
@@ -56,7 +57,7 @@ function whitw2(bb, chess, def) {
|
||||
let best = null, bd = Infinity, bh = Infinity;
|
||||
for (const e of battle.enemies) {
|
||||
if (!ok(e)) continue;
|
||||
const de = Math.hypot(e.x - d.x, e.y - d.y), dh = Math.hypot(e.x - unit.x, e.y - unit.y);
|
||||
const de = hypot(e.x - d.x, e.y - d.y), dh = hypot(e.x - unit.x, e.y - unit.y);
|
||||
if (de < bd - 1e-9 || (de <= bd + 1e-9 && dh < bh - 1e-9)) { best = e; bd = de; bh = dh; }
|
||||
}
|
||||
return best;
|
||||
@@ -65,9 +66,9 @@ function whitw2(bb, chess, def) {
|
||||
const circle = (d, dt) => {
|
||||
const { r, v } = WHITW2_ORBIT;
|
||||
if (!d.orbit) d.orbit = { cx: d.x - d.hy * r, cy: d.y + d.hx * r };
|
||||
const a = Math.atan2(d.y - d.orbit.cy, d.x - d.orbit.cx) + (v / r) * dt;
|
||||
d.x = d.orbit.cx + r * Math.cos(a); d.y = d.orbit.cy + r * Math.sin(a);
|
||||
d.hx = -Math.sin(a); d.hy = Math.cos(a);
|
||||
const a = atan2(d.y - d.orbit.cy, d.x - d.orbit.cx) + (v / r) * dt;
|
||||
d.x = d.orbit.cx + r * cos(a); d.y = d.orbit.cy + r * sin(a);
|
||||
d.hx = -sin(a); d.hy = cos(a);
|
||||
};
|
||||
// one tick of one drone: spread → (pick) → chase → on the target, attacking like a normal drone. [ASSUMED] its attack
|
||||
// clock runs all the time (one attack per interval of hers at most, whatever it chased in between) and its first hit
|
||||
@@ -103,7 +104,7 @@ function whitw2(bb, chess, def) {
|
||||
if (d.phase === 'chase') {
|
||||
const s = accelerate(d, WHITW2_CHASE, dt);
|
||||
if (bodyDist(t, d.x, d.y) > s + 1e-9) {
|
||||
const dx = t.x - d.x, dy = t.y - d.y, L = Math.hypot(dx, dy);
|
||||
const dx = t.x - d.x, dy = t.y - d.y, L = hypot(dx, dy);
|
||||
if (L > 1e-9) { d.hx = dx / L; d.hy = dy / L; }
|
||||
d.x += d.hx * s; d.y += d.hy * s;
|
||||
return;
|
||||
|
||||
@@ -63,6 +63,7 @@ import { bodyInKeys } from '../../../body.js';
|
||||
import { hasHp } from '../../../damage.js';
|
||||
import { RESIST_STATUSES } from '../../../buffs.js';
|
||||
import { TICK } from '../../../constants.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
const S1 = 'skchr_aglna2_1';
|
||||
const S2 = 'skchr_aglna2_2';
|
||||
@@ -342,7 +343,7 @@ export default {
|
||||
if (!st || !skillOn(unit, S3) || !up(unit) || st.flight || !unit.canAct || unit.blocking.length) return;
|
||||
const tgt = moveTarget(battle, unit);
|
||||
if (!tgt) return;
|
||||
const dur = Math.hypot(tgt.r - unit.tileR, tgt.c - unit.tileC) / MOVE_SPEED;
|
||||
const dur = hypot(tgt.r - unit.tileR, tgt.c - unit.tileC) / MOVE_SPEED;
|
||||
const act = unit.skill.activations;
|
||||
st.flight = tgt;
|
||||
battle.releaseBlocked(unit);
|
||||
|
||||
@@ -62,6 +62,7 @@ import { sortEnemyTargets, canTargetEnemy } from '../../../targeting.js';
|
||||
import { bodyInKeys } from '../../../body.js';
|
||||
import { hasHp, isHpLoss } from '../../../damage.js';
|
||||
import { COLS, PROJECTILE_SPEEDS } from '../../../constants.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
const S1 = 'skchr_cqbw_1';
|
||||
const S2 = 'skchr_cqbw_2';
|
||||
@@ -133,7 +134,7 @@ function mineTile(battle, unit) {
|
||||
let best = [], bd = Infinity;
|
||||
for (const t of tiles) {
|
||||
let d = Infinity;
|
||||
for (const e of foes) d = Math.min(d, Math.hypot(e.x - t[1], e.y - t[0]));
|
||||
for (const e of foes) d = Math.min(d, hypot(e.x - t[1], e.y - t[0]));
|
||||
if (d < bd - 1e-9) { bd = d; best = [t]; } else if (Math.abs(d - bd) <= 1e-9) best.push(t);
|
||||
}
|
||||
return best.length === 1 ? best[0] : battle.rng.pick(best);
|
||||
|
||||
@@ -67,6 +67,7 @@ import { num, talentBb, moduleBb, traitBb, skillRec, batMod, up, toggleBuff } fr
|
||||
import { canTargetEnemy, sortEnemyTargets } from '../../../targeting.js';
|
||||
import { hasHp } from '../../../damage.js';
|
||||
import { COLS } from '../../../constants.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
const S1 = 'skchr_ebnhlz_1';
|
||||
const S2 = 'skchr_ebnhlz_2';
|
||||
@@ -116,7 +117,7 @@ function remnantTiles(battle, unit) {
|
||||
const rank = new Map();
|
||||
foes.forEach((e, i) => { const k = Math.round(e.y) * COLS + Math.round(e.x); if (!rank.has(k)) rank.set(k, i); });
|
||||
battle.rng.shuffle(tiles); // the random order of the last group, and of equal distances [ASSUMED]
|
||||
const dist = (r, c) => { let d = Infinity; for (const e of foes) d = Math.min(d, Math.hypot(e.x - c, e.y - r)); return d; };
|
||||
const dist = (r, c) => { let d = Infinity; for (const e of foes) d = Math.min(d, hypot(e.x - c, e.y - r)); return d; };
|
||||
const keyed = tiles.map(([r, c], i) => ({ r, c, i, on: rank.get(r * COLS + c) ?? Infinity, d: dist(r, c) }));
|
||||
keyed.sort((a, b) => a.on - b.on || a.d - b.d || a.i - b.i);
|
||||
return keyed.map((x) => [x.r, x.c]);
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
import { num, talentBb, moduleBb, moduleOn, skillRec, statBuff, toggleBuff, up } from '../shared/tier1.js';
|
||||
import { toLocal } from '../../../dir.js';
|
||||
import { COLS } from '../../../constants.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
const S1 = 'skchr_haak_1';
|
||||
const S2 = 'skchr_haak_2';
|
||||
@@ -80,7 +81,7 @@ export function frontAlly(battle, unit, heal = false) {
|
||||
if (heal && (a.s.flags.noHeal || a.s.flags.healFree || a.profile?.noHeal)) continue;
|
||||
const dr = a.tileR - unit.tileR, dc = a.tileC - unit.tileC;
|
||||
const [lr, lc] = toLocal(dr, dc, unit.dir);
|
||||
const k = [lr === 0 && lc > 0 ? 0 : 1, Math.hypot(dr, dc), -(a.s.taunt || 0), -(a.aggroSeq || a.deploySeq || 0), a.id];
|
||||
const k = [lr === 0 && lc > 0 ? 0 : 1, hypot(dr, dc), -(a.s.taunt || 0), -(a.aggroSeq || a.deploySeq || 0), a.id];
|
||||
let less = !bk;
|
||||
for (let i = 0; !less && i < k.length; i++) {
|
||||
if (k[i] === bk[i]) continue;
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
import { num, up, traitBb, talentBb, skillRec, batMod, giveSp, protectMods } from '../shared/tier1.js';
|
||||
import { absoluteRangeKeys } from '../../../targeting.js';
|
||||
import { COLS } from '../../../constants.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
const S1 = 'skchr_haruka_1';
|
||||
const S2 = 'skchr_haruka_2';
|
||||
@@ -217,7 +218,7 @@ export default {
|
||||
if (c.source !== unit || c.opts?.regen || !skillOn(unit, S2) || !(c.amount > 0) || !(scale > 0) || !n) return;
|
||||
const t = c.target;
|
||||
const foes = battle.foesInRadius(t.x, t.y, r);
|
||||
foes.sort((x, y) => Math.hypot(x.x - t.x, x.y - t.y) - Math.hypot(y.x - t.x, y.y - t.y) || x.spawnSeq - y.spawnSeq);
|
||||
foes.sort((x, y) => hypot(x.x - t.x, x.y - t.y) - hypot(y.x - t.x, y.y - t.y) || x.spawnSeq - y.spawnSeq);
|
||||
const amount = c.amount * scale;
|
||||
for (const e of foes.slice(0, n)) battle.dealDamage(unit, e, { amount, type: 'arts', isSkill: true, tags: ['skill', 'haruka:s2'] });
|
||||
if (foes.length) battle.fx('aoe', { x: t.x, y: t.y, radius: r, id: unit.id, skill: 'haruka:s2' });
|
||||
|
||||
@@ -57,6 +57,7 @@ import { canTargetEnemy } from '../../../targeting.js';
|
||||
import { bodyInRadius } from '../../../body.js';
|
||||
import { hasHp } from '../../../damage.js';
|
||||
import { COLS } from '../../../constants.js';
|
||||
import { sin, cos, atan2 } from '../../../detmath.js';
|
||||
|
||||
const S1 = 'skchr_hsgma2_1';
|
||||
const S2 = 'skchr_hsgma2_2';
|
||||
@@ -98,7 +99,7 @@ export default {
|
||||
/** S2: the shield's centre after `turned` degrees (row 0 is the bottom row: counter-clockwise = a growing angle). */
|
||||
const shieldAt = (unit, sh) => {
|
||||
const a = sh.a0 + (sh.turned * Math.PI) / 180;
|
||||
return { x: unit.x + SHIELD_START * Math.cos(a), y: unit.y + SHIELD_START * Math.sin(a) };
|
||||
return { x: unit.x + SHIELD_START * cos(a), y: unit.y + SHIELD_START * sin(a) };
|
||||
};
|
||||
const touching = (battle, unit, p) => battle.enemies.filter((e) => canTargetEnemy(unit, e, GROUND) && bodyInRadius(e, p.x, p.y, SHIELD_RADIUS));
|
||||
/** S2: one shield pulse — 停顿, the arts hit, and her heal of heal_ratio × the HP it removed (ignoring her 禁疗). */
|
||||
@@ -128,7 +129,7 @@ export default {
|
||||
if (!('hits' in s2Attack)) return;
|
||||
plainS2(); // the throw was this attack: the shield flies from now on
|
||||
const [fr, fc] = unit.fwd;
|
||||
unit.mem.hsShield = { a0: Math.atan2(fr, fc), turned: 0, acc: 0 };
|
||||
unit.mem.hsShield = { a0: atan2(fr, fc), turned: 0, acc: 0 };
|
||||
battle.fx('zone', { x: unit.x, y: unit.y, radius: SHIELD_START + SHIELD_RADIUS, dur: SHIELD_TURN / SHIELD_SPEED, id: unit.id, skill: 'hsgma2:shield' });
|
||||
shieldPulse(battle, unit, shieldAt(unit, unit.mem.hsShield));
|
||||
},
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
import { num, talentBb, moduleBb, traitBb, skillRec, up, giveSp } from '../shared/tier1.js';
|
||||
import { bodyInKeys } from '../../../body.js';
|
||||
import { hasHp } from '../../../damage.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
const S1 = 'skchr_ifrit_1';
|
||||
const S2 = 'skchr_ifrit_2';
|
||||
@@ -197,7 +198,7 @@ export default {
|
||||
battle.on('hit', (c) => {
|
||||
const e = c.target;
|
||||
if (c.source !== unit || !e || e.side !== 'enemy' || c.dmg.type === 'element' || !up(unit)) return;
|
||||
const d = Math.hypot(e.x - unit.x, e.y - unit.y);
|
||||
const d = hypot(e.x - unit.x, e.y - unit.y);
|
||||
c.dmg.mul *= 1 + ds * (hi > lo ? Math.max(0, Math.min(1, (d - lo) / (hi - lo))) : 1);
|
||||
}, { owner: unit });
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
import { num, talentBb, traitBb, moduleOn, skillRec, batMod, up, giveSp, installReveal } from '../shared/tier1.js';
|
||||
import { offsetTile, dirFromDelta, dirVec } from '../../../dir.js';
|
||||
import { isHpLoss } from '../../../damage.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
const S1 = 'skchr_jesca2_1';
|
||||
const S2 = 'skchr_jesca2_2';
|
||||
@@ -182,7 +183,7 @@ function fireBomb(battle, unit, b3) {
|
||||
};
|
||||
(unit.mem.jescaShells ??= []).push(bomb);
|
||||
unit.atkCd = unit.s.interval; // the shell is her attack: the timer restarts [ASSUMED]
|
||||
battle.fx('bombardShell', { x: bomb.tx, y: bomb.ty, id: unit.id, r: bomb.radius, t: Math.hypot(bomb.tx - bomb.x, bomb.ty - bomb.y) / BOMB_SPEED });
|
||||
battle.fx('bombardShell', { x: bomb.tx, y: bomb.ty, id: unit.id, r: bomb.radius, t: hypot(bomb.tx - bomb.x, bomb.ty - bomb.y) / BOMB_SPEED });
|
||||
}
|
||||
|
||||
/** Move the shells one tick; a shell touching an enemy or reaching its end point explodes. */
|
||||
@@ -192,7 +193,7 @@ function flyShells(battle, unit, dt) {
|
||||
const keep = [];
|
||||
for (const s of list) {
|
||||
const dx = s.tx - s.x, dy = s.ty - s.y;
|
||||
const d = Math.hypot(dx, dy);
|
||||
const d = hypot(dx, dy);
|
||||
const step = BOMB_SPEED * dt;
|
||||
let hit = battle.foesInRadius(s.x, s.y, BOMB_HIT_RADIUS).length > 0;
|
||||
if (!hit) {
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
import { num, talentBb, skillRec, batMod, up } from '../shared/tier1.js';
|
||||
import { releaseSkillSummon } from '../../tokens.js';
|
||||
import { COLS } from '../../../constants.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
const S1 = 'skchr_kalts2_1';
|
||||
const S2 = 'skchr_kalts2_2';
|
||||
@@ -235,7 +236,7 @@ function startTransport(battle, unit, anchor) {
|
||||
}, { owner: unit });
|
||||
}
|
||||
const from = { x: unit.x, y: unit.y }, to = { x: anchor.x, y: anchor.y };
|
||||
const T = Math.hypot(to.x - from.x, to.y - from.y) / TRANSPORT_SPEED;
|
||||
const T = hypot(to.x - from.x, to.y - from.y) / TRANSPORT_SPEED;
|
||||
const f = { t0: battle.time, T, from, to, hit: new Set(), anchor, r: anchor.tileR, c: anchor.tileC, dir: anchor.dir };
|
||||
unit.mem.k2flight = f;
|
||||
battle.releaseBlocked(unit);
|
||||
|
||||
@@ -46,6 +46,7 @@ import { hasHp } from '../../../damage.js';
|
||||
import { canTargetEnemy, enemyStealthed } from '../../../targeting.js';
|
||||
import { bodyInKeys } from '../../../body.js';
|
||||
import { COLS, PALSY_MAX } from '../../../constants.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
const S1 = 'skchr_mantra_1';
|
||||
const S2 = 'skchr_mantra_2';
|
||||
@@ -91,7 +92,7 @@ function nearestFoe(battle, unit, x, y, r, skip) {
|
||||
let best = null, bd = Infinity;
|
||||
for (const e of battle.enemiesInRadius(x, y, r)) {
|
||||
if (skip.has(e) || !canTargetEnemy(unit, e, ANY)) continue;
|
||||
const d = Math.hypot(e.x - x, e.y - y);
|
||||
const d = hypot(e.x - x, e.y - y);
|
||||
if (d < bd - 1e-9 || (Math.abs(d - bd) <= 1e-9 && best && e.spawnSeq < best.spawnSeq)) { bd = d; best = e; }
|
||||
}
|
||||
return best;
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
import { num, skillRec, batMod, up } from '../shared/tier1.js';
|
||||
import { COLS } from '../../../constants.js';
|
||||
import { dirVec } from '../../../dir.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
const S1 = 'skchr_mcnist_1';
|
||||
const S2 = 'skchr_mcnist_2';
|
||||
@@ -197,7 +198,7 @@ function grafKit(owner, { life, t2, b2, b3, aura }) {
|
||||
function chargeGraf(battle, mcn, t, b3) {
|
||||
if (!up(t)) return;
|
||||
const { stop, land } = chargeEnd(battle, t);
|
||||
const dist = Math.hypot(stop[0] - t.tileR, stop[1] - t.tileC);
|
||||
const dist = hypot(stop[0] - t.tileR, stop[1] - t.tileC);
|
||||
t.mem.charging = true;
|
||||
battle.fx('charge', { x: t.x, y: t.y, id: t.id, tx: stop[1], ty: stop[0] });
|
||||
battle.retreat(t, { reason: 'expired', permanent: true });
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
import { num, talentBb, moduleBb, traitBb, skillRec, statBuff, toggleBuff, onHitBy, enemiesInGrid, cheb, up, giveSp } from '../shared/tier1.js';
|
||||
import { canTargetEnemy } from '../../../targeting.js';
|
||||
import { isHpLoss } from '../../../damage.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
const S1 = 'skchr_narant_1';
|
||||
const S2 = 'skchr_narant_2';
|
||||
@@ -138,7 +139,7 @@ export default {
|
||||
if (left <= 0 || !ctx.home()) { ctx.comeBack(x, y); return; }
|
||||
const cands = battle.foesInRadius(x, y, BOUNCE_RADIUS).filter((e) => e !== on && canTargetEnemy(unit, e, ctx.profile));
|
||||
if (!cands.length) { ctx.comeBack(x, y); return; }
|
||||
const d2 = (e) => (e.x - x) ** 2 + (e.y - y) ** 2;
|
||||
const d2 = (e) => (e.x - x) * (e.x - x) + (e.y - y) * (e.y - y);
|
||||
cands.sort((a, b) => (hit.has(a) ? 1 : 0) - (hit.has(b) ? 1 : 0) || d2(a) - d2(b) || a.spawnSeq - b.spawnSeq);
|
||||
const tgt = cands[0];
|
||||
left--;
|
||||
@@ -158,7 +159,7 @@ export default {
|
||||
const dash = (ctx) => {
|
||||
const { battle, unit } = ctx;
|
||||
let dx = ctx.x - unit.x, dy = ctx.y - unit.y;
|
||||
const len = Math.hypot(dx, dy);
|
||||
const len = hypot(dx, dy);
|
||||
if (len > 1e-9) { dx /= len; dy /= len; } else { const [fr, fc] = unit.fwd; dx = fc; dy = fr; }
|
||||
const ahead = DASH_SPEED * Math.max(0, num(b2['attack@move_ahead_time'], 0.5));
|
||||
battle.addProjectile({ from: { x: ctx.x, y: ctx.y }, to: { x: ctx.x + dx * ahead, y: ctx.y + dy * ahead }, speed: DASH_SPEED, visual: 'boomerang', source: unit, hitDead: true,
|
||||
|
||||
@@ -68,6 +68,7 @@ import { absoluteRangeKeys } from '../../../targeting.js';
|
||||
import { bodyInKeys } from '../../../body.js';
|
||||
import { hasHp, burstLocked } from '../../../damage.js';
|
||||
import { MIN_DAMAGE_RATIO } from '../../../constants.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
const S1 = 'skchr_phatm2_1';
|
||||
const S2 = 'skchr_phatm2_2';
|
||||
@@ -134,7 +135,7 @@ function callKit(t, owner, b2) {
|
||||
const keys = keysAt(grid, tok);
|
||||
const cands = battle.enemiesInKeys([...keys], tok, ANY).filter((e) => !lured.includes(e) && !e.s.flags.attract
|
||||
&& (e.motion === 'FLY' || !!battle.grid.waypoints(Math.round(e.y), Math.round(e.x), tok.tileR, tok.tileC)));
|
||||
cands.sort((a, b) => (isElite(b) - isElite(a)) || (Math.hypot(a.x - tok.x, a.y - tok.y) - Math.hypot(b.x - tok.x, b.y - tok.y)) || a.spawnSeq - b.spawnSeq);
|
||||
cands.sort((a, b) => (isElite(b) - isElite(a)) || (hypot(a.x - tok.x, a.y - tok.y) - hypot(b.x - tok.x, b.y - tok.y)) || a.spawnSeq - b.spawnSeq);
|
||||
for (const e of cands) {
|
||||
if (lured.length >= cap) break;
|
||||
if (battle.applyStatus(e, 'attract', { duration: Math.max(0.05, endAt - battle.time), source: tok, point: [tok.tileR, tok.tileC] })) {
|
||||
@@ -146,7 +147,7 @@ function callKit(t, owner, b2) {
|
||||
// the first lured enemy within `arrive` of it: it leaves
|
||||
battle.on('tick', () => {
|
||||
if (!up(tok)) return;
|
||||
if (lured.some((e) => e.alive && mine(e) && Math.hypot(e.x - tok.x, e.y - tok.y) <= arrive + 1e-9)) battle.retreat(tok, { reason: 'expired', permanent: true });
|
||||
if (lured.some((e) => e.alive && mine(e) && hypot(e.x - tok.x, e.y - tok.y) <= arrive + 1e-9)) battle.retreat(tok, { reason: 'expired', permanent: true });
|
||||
}, { owner: tok });
|
||||
// leaving (any reason): 停顿 + the damage over time on every selectable enemy of its x-1; the 诱导 ends
|
||||
battle.on('death', (c) => {
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
import { num, talentBb, traitBb, moduleOn, skillRec, up, statBuff } from '../shared/tier1.js';
|
||||
import { toLocal } from '../../../dir.js';
|
||||
import { COLS, PROJECTILE_SPEEDS } from '../../../constants.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
const S1 = 'skchr_phenxi_1';
|
||||
const S2 = 'skchr_phenxi_2';
|
||||
@@ -136,7 +137,7 @@ function s3Shot(battle, unit, b3) {
|
||||
battle.addProjectile({ from: unit, to: { x, y }, speed: S3_SPEED, visual: 'bomb', source: unit, onHit: () => {
|
||||
battle.fx('aoe', { x, y, radius: S3_RADIUS, id: unit.id, skill: 'phenxi:s3' });
|
||||
for (const e of battle.foesInRadius(x, y, S3_RADIUS, true)) {
|
||||
const sc = Math.hypot(e.x - x, e.y - y) <= core + 1e-9 ? hi : lo;
|
||||
const sc = hypot(e.x - x, e.y - y) <= core + 1e-9 ? hi : lo;
|
||||
battle.dealDamage(unit, e, { amount: unit.s.atk * sc * unit.s.atkScaleMul, type: 'phys', isAttack: true, isSkill: true, isSplash: true, tags: ['skill', 'phenxi:s3'] });
|
||||
}
|
||||
} });
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
|
||||
import { num, talentBb, moduleBb, moduleOn, traitBb, skillRec, statBuff, onHitBy, enemiesInGrid, up } from '../shared/tier1.js';
|
||||
import { hasHp } from '../../../damage.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
const S1 = 'skcom_atk_up[3]';
|
||||
const S2 = 'skchr_poca_2';
|
||||
@@ -161,7 +162,7 @@ export default {
|
||||
if (ds > 0) {
|
||||
onHitBy(battle, unit, ({ target, dmg }) => {
|
||||
if (!dmg.isAttack) return;
|
||||
const d = Math.hypot(target.x - unit.x, target.y - unit.y);
|
||||
const d = hypot(target.x - unit.x, target.y - unit.y);
|
||||
dmg.mul *= 1 + ds * Math.max(0, Math.min(1, (d - lo) / Math.max(1e-6, hi - lo)));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ import { acquireTargets } from '../../../ai.js';
|
||||
import { aggregateMods } from '../../../buffs.js';
|
||||
import { dirVec } from '../../../dir.js';
|
||||
import { COLS } from '../../../constants.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
const S1 = 'skchr_radian_1';
|
||||
const S2 = 'skchr_radian_2';
|
||||
@@ -160,7 +161,7 @@ function flyBullets(battle, t, dt) {
|
||||
const hit = () => {
|
||||
const near = battle.foesInRadius(b.x, b.y, BULLET_RADIUS).filter((e) => e.alive && !e.isFlying);
|
||||
if (!near.length) return false;
|
||||
near.sort((p, q) => Math.hypot(p.x - b.x, p.y - b.y) - Math.hypot(q.x - b.x, q.y - b.y) || p.spawnSeq - q.spawnSeq);
|
||||
near.sort((p, q) => hypot(p.x - b.x, p.y - b.y) - hypot(q.x - b.x, q.y - b.y) || p.spawnSeq - q.spawnSeq);
|
||||
battle.dealDamage(t, near[0], { amount: b.atk, type: 'phys', isAttack: true, tags: [TAG_BULLET] });
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -63,6 +63,7 @@ import { sortEnemyTargets } from '../../../targeting.js';
|
||||
import { bodyInKeys } from '../../../body.js';
|
||||
import { PUSH_DIRECTIONAL_MIN_DIST } from '../../../constants.js';
|
||||
import { startCountdown } from '../../tokens.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
const S1 = 'skchr_ray_1';
|
||||
const S2 = 'skchr_ray_2';
|
||||
@@ -159,7 +160,7 @@ export default {
|
||||
if (!target.alive) { unit.mem.rayLoadExtra = num(unit.mem.rayLoadExtra) + Math.max(0, Math.floor(num(b1.cnt, 1))); return; }
|
||||
const force = num(b1.force, 1);
|
||||
const [fr, fc] = unit.fwd;
|
||||
const vx = target.x - unit.x, vy = target.y - unit.y, d = Math.hypot(vx, vy);
|
||||
const vx = target.x - unit.x, vy = target.y - unit.y, d = hypot(vx, vy);
|
||||
if (d >= PUSH_DIRECTIONAL_MIN_DIST && vx * fc + vy * fr >= d * Math.SQRT1_2) battle.push(target, force, { from: unit, dir: { x: fc, y: fr }, fixed: true });
|
||||
else battle.push(target, force - 1, { from: unit });
|
||||
},
|
||||
|
||||
@@ -58,6 +58,7 @@ import { num, talentBb, moduleBb, traitBb, moduleOn, skillRec, up } from '../sha
|
||||
import { absoluteRangeKeys, sortEnemyTargets } from '../../../targeting.js';
|
||||
import { offsetTile } from '../../../dir.js';
|
||||
import { COLS } from '../../../constants.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
const S1 = 'skcom_assist_cost[3]';
|
||||
const S2 = 'skchr_sleach_2';
|
||||
@@ -150,7 +151,7 @@ function s3Tile(battle, unit, grid) {
|
||||
for (const k of keys) {
|
||||
const r = (k / COLS) | 0, c = k % COLS;
|
||||
if (!ground(r, c) || battle.unitAt(r, c)) continue;
|
||||
const d = Math.hypot(c - top.x, r - top.y);
|
||||
const d = hypot(c - top.x, r - top.y);
|
||||
if (d < bd - 1e-9) { bd = d; best = [r, c]; }
|
||||
}
|
||||
return best ?? [unit.tileR, unit.tileC];
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
import { num, talentBb, moduleOn, traitBb, skillRec, batMod, onHitBy, skillBusy, up } from '../shared/tier1.js';
|
||||
import { canTargetEnemy, sortEnemyTargets } from '../../../targeting.js';
|
||||
import { ROWS, COLS } from '../../../constants.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
const S1 = 'skcom_quickattack[3]';
|
||||
const S2 = 'skchr_typhon_2';
|
||||
@@ -204,7 +205,7 @@ export default {
|
||||
if (ds > 0) {
|
||||
onHitBy(battle, unit, ({ target, dmg }) => {
|
||||
if (!dmg.isAttack) return;
|
||||
const d = Math.hypot(target.x - unit.x, target.y - unit.y);
|
||||
const d = hypot(target.x - unit.x, target.y - unit.y);
|
||||
dmg.mul *= 1 + ds * Math.max(0, Math.min(1, (d - lo) / Math.max(1e-6, hi - lo)));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
|
||||
import { num, talentBb, moduleBb, traitBb, skillRec, batMod, toggleBuff } from '../shared/tier1.js';
|
||||
import { canTargetEnemy, sortEnemyTargets } from '../../../targeting.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
const S1 = 'skchr_veen_1';
|
||||
const S2 = 'skchr_veen_2';
|
||||
@@ -115,7 +116,7 @@ export default {
|
||||
if (!pool.length) pool = near;
|
||||
let next = null, bd = Infinity;
|
||||
for (const e of pool) {
|
||||
const d = Math.hypot(e.x - cur.x, e.y - cur.y);
|
||||
const d = hypot(e.x - cur.x, e.y - cur.y);
|
||||
if (d < bd - 1e-9 || (Math.abs(d - bd) <= 1e-9 && next && e.spawnSeq < next.spawnSeq)) { bd = d; next = e; }
|
||||
}
|
||||
if (!next) return; // nobody within reach: the bounces left are lost
|
||||
|
||||
@@ -49,6 +49,7 @@ import { num, traitBb, skillRec, up, giveSp } from '../shared/tier1.js';
|
||||
import { absoluteRangeKeys } from '../../../targeting.js';
|
||||
import { COLS } from '../../../constants.js';
|
||||
import { startCountdown } from '../../tokens.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
const S1 = 'skchr_weedy_1';
|
||||
const S2 = 'skchr_weedy_2';
|
||||
@@ -92,7 +93,7 @@ export function rupture(battle, src, e, { duration, perTile, interval }) {
|
||||
return;
|
||||
}
|
||||
const st = { x: e.x, y: e.y, moved: 0, acc: 0 };
|
||||
const step = (u) => { st.moved += Math.hypot(u.x - st.x, u.y - st.y); st.x = u.x; st.y = u.y; };
|
||||
const step = (u) => { st.moved += hypot(u.x - st.x, u.y - st.y); st.x = u.x; st.y = u.y; };
|
||||
const flush = (u) => {
|
||||
const m = st.moved;
|
||||
st.moved = 0;
|
||||
@@ -121,7 +122,7 @@ export function liquidNitrogen(battle, { shooter, src, atk, sbb, grid, force, pi
|
||||
const keys = absoluteRangeKeys(grid ?? GRID_4_1, shooter.tileR, shooter.tileC, shooter.dir, 0);
|
||||
const cands = battle.enemiesInKeys(keys, shooter, { canHitFly: true });
|
||||
if (!cands.length) return false;
|
||||
const key = pick === 'nearest' ? (e) => Math.hypot(e.x - shooter.x, e.y - shooter.y) : (e) => battle.remainingDistance(e);
|
||||
const key = pick === 'nearest' ? (e) => hypot(e.x - shooter.x, e.y - shooter.y) : (e) => battle.remainingDistance(e);
|
||||
let target = null, best = Infinity;
|
||||
for (const e of cands) {
|
||||
const v = key(e);
|
||||
|
||||
@@ -61,6 +61,7 @@ import { num, talentBb, traitBb, skillRec, up, batMod } from '../shared/tier1.js
|
||||
import { absoluteRangeKeys, sortEnemyTargets } from '../../../targeting.js';
|
||||
import { hasHp } from '../../../damage.js';
|
||||
import { COLS } from '../../../constants.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
|
||||
const S1 = 'skchr_wisdel_1';
|
||||
const S2 = 'skchr_wisdel_2';
|
||||
@@ -138,7 +139,7 @@ function shadowTile(battle, host) {
|
||||
for (const k of host.rangeKeys ?? []) {
|
||||
const r = Math.floor(k / COLS), c = k % COLS;
|
||||
if (!battle.grid.inRect(r, c) || !battle.grid.canStand(r, c, { ranged: true }) || battle.isReservedTile(r, c)) continue;
|
||||
const d = Math.hypot(r - host.tileR, c - host.tileC);
|
||||
const d = hypot(r - host.tileR, c - host.tileC);
|
||||
if (d < bd - 1e-9 || (Math.abs(d - bd) <= 1e-9 && (r < best[0] || (r === best[0] && c < best[1])))) { bd = d; best = [r, c]; }
|
||||
}
|
||||
return best;
|
||||
|
||||
Reference in new issue
Block a user