GitHub PR #281 by @IceCodeNew (this commit is substantially the PR, ported onto 0.2.2-dev; the field-detail range idea after @BBleae's fork commits c6437f1 / b451bad, credited here instead of the PR's Co-authored-by trailers). The unofficial skill-range preview the PR first had is gone (the owner's reply of 2026-10-07: only official-like behaviour); what remains is the range the unit fights with now. screens/game/range.js inspectRange resolves the open card's unit: an own board piece in prep (its loadout or stand-in record, its facing — this replaces the prep selection's range effect); in battle or on a scouted board an ally op / token on the field, at the snapshot's tile, with the live stats' range when there is one; never an enemy, a dead or undeployed unit, or a scouted bench unit (UnitInfo `area` hand / temp — the tag the server sends since the #385 fix; renderInfo now keeps it). shared/protocol.js unitStatsEntry gains an optional `dir` (the unit's facing now: the snapshot tuples carry none and UnitInfo.dir is the facing at send time). tokenVariantFor moves to gameLogic/loadout.js. Drag, the wheel and the pen clear the overlay. Added on top of the PR: a test from the server's own scout m.field (board operator ranged, hand / temp none), docs (PLAYING.md, design/client.md, design/network.md), comments. Tests: test/ui/inspect-range.test.js (6), test/ui/playtest6_summons.test.js, test/match/unitStats.test.js; test/ui/inspect-range.e2e.test.js (SP_E2E + CHROME_PATH: run here, 1/1). Golden unchanged (display; m.unitStats only).
33 lines
2.1 KiB
JavaScript
33 lines
2.1 KiB
JavaScript
// screens/game/range.js — the attack range the open detail card's unit draws on the field (GitHub PR #281 by
|
|
// @IceCodeNew; the field-detail range idea after @BBleae's fork): see game.js cardRange.
|
|
import { attackRangeGrid } from '../../../../shared/loadoutRecord.js';
|
|
import { chessLoadout, standInLoadout } from '../../ui/gameLogic.js';
|
|
import { tokenVariantFor } from '../../ui/gameLogic/loadout.js';
|
|
import { normDir } from '../../ui/facing.js';
|
|
|
|
/** Resolve the inspected field unit without granting placement or sale actions. */
|
|
export function inspectRange({ target, detail, pieces, showPrep = false, field, snapshot, live, loadout, getChess, backups }) {
|
|
if (!detail || (detail.type !== 'chess' && detail.type !== 'token')) return null;
|
|
let row, col, dir;
|
|
if (target?.kind === 'piece' && showPrep) {
|
|
const e = pieces?.get(target.uid);
|
|
if (!e || e.area !== 'board') return null;
|
|
row = e.row; col = e.col; dir = normDir(e.piece.dir);
|
|
} else if (target?.kind === 'unit' && !showPrep) {
|
|
const unit = target.unit;
|
|
if (!unit || unit.side !== 'ally' || (unit.kind !== 'op' && unit.kind !== 'token')) return null;
|
|
if (unit.area != null && unit.area !== 'board') return null;
|
|
// Snapshots exclude undeployed, hidden and removed units. Dead units remain briefly for their death animation.
|
|
if (!field?.prep && (!snapshot || snapshot[3] <= 0)) return null;
|
|
row = Math.round(snapshot ? snapshot[2] : unit.y);
|
|
col = Math.round(snapshot ? snapshot[1] : unit.x);
|
|
dir = normDir(live?.dir, null) || normDir(unit.dir, null) || (unit.facing === -1 ? 'LEFT' : 'RIGHT');
|
|
} else return null;
|
|
if (!Number.isFinite(row) || !Number.isFinite(col)) return null;
|
|
const record = detail.type === 'token' ? { ...detail.token, ...tokenVariantFor(detail.token, detail.ownerId) }
|
|
: detail.standIn ? standInLoadout(detail.standIn, getChess, backups)?.record || detail.standIn
|
|
: chessLoadout(detail.chess, detail.diy ? null : loadout, getChess)?.record || detail.chess;
|
|
const grid = Array.isArray(live?.range) ? live.range : attackRangeGrid(record);
|
|
return grid?.length ? { grid, row, col, dir } : null;
|
|
}
|