Updated Battle Scanner
This commit is contained in:
parent
c8e22a51a5
commit
cba84dc0b8
|
|
@ -1074,8 +1074,25 @@ export default class MasterOfVegaGame extends Phaser.Scene {
|
||||||
if (this.modalOpen || this.busy) return;
|
if (this.modalOpen || this.busy) return;
|
||||||
// Someone else's fleet is not something we can give orders to, but the
|
// Someone else's fleet is not something we can give orders to, but the
|
||||||
// system it is sitting in is — clicking it reads as clicking that system,
|
// system it is sitting in is — clicking it reads as clicking that system,
|
||||||
// which with a fleet in hand is how an attack gets ordered.
|
// which with a fleet in hand is how an attack gets ordered. That takes
|
||||||
|
// priority over sensor detail below: a fleet already selected for a move
|
||||||
|
// order must still route through onStarClick's "quote a route" flow
|
||||||
|
// (same condition onStarClick itself uses), not get swallowed by the
|
||||||
|
// read-only scan panel.
|
||||||
if (fleet.empireIdx !== this.state.humanIndex) {
|
if (fleet.empireIdx !== this.state.humanIndex) {
|
||||||
|
const ordering = this.selectedFleet && this.state.fleets.includes(this.selectedFleet)
|
||||||
|
&& this.selectedFleet.starIdx >= 0 && this.selectedFleet.starIdx !== fleet.starIdx;
|
||||||
|
// Detected on sensors (Battle Scanner and friends' scanRange effect,
|
||||||
|
// Brian's ask, 2026-08-14): the same fleet panel our own fleets use,
|
||||||
|
// just read-only (VegaSidePanel.js's buildFleet branches on
|
||||||
|
// ownership). Outside scan range, or mid-order, a rival fleet marker
|
||||||
|
// still just reads as "click the system," same as before scanRange
|
||||||
|
// existed.
|
||||||
|
if (!ordering && Logic.fleetInScanRange(this.rules, this.state, this.state.humanIndex, fleet)) {
|
||||||
|
playSound(this, SFX.VEGA_UNIT);
|
||||||
|
this.panel.showFleet(fleet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (fleet.starIdx >= 0) this.onStarClick(fleet.starIdx);
|
if (fleet.starIdx >= 0) this.onStarClick(fleet.starIdx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,9 @@
|
||||||
// Ecology cleans industrial waste; Defence raises planetary defences; Research
|
// Ecology cleans industrial waste; Defence raises planetary defences; Research
|
||||||
// splits into the six tech fields.
|
// splits into the six tech fields.
|
||||||
|
|
||||||
import { generateGalaxy, parsecs, mulberry32 } from './VegaGalaxyGen.js';
|
import {
|
||||||
|
generateGalaxy, parsecs, mulberry32, PARSEC_PX,
|
||||||
|
} from './VegaGalaxyGen.js';
|
||||||
import { techCost, techCostFactor } from './VegaRules.js';
|
import { techCost, techCostFactor } from './VegaRules.js';
|
||||||
import { designFor, bestComponents, markFor, refitCost } from './VegaShips.js';
|
import { designFor, bestComponents, markFor, refitCost } from './VegaShips.js';
|
||||||
// Space combat is resolved by the V2 per-ship engine (formerly a ?movsim-only
|
// Space combat is resolved by the V2 per-ship engine (formerly a ?movsim-only
|
||||||
|
|
@ -359,6 +361,66 @@ export function reachableStars(rules, state, e) {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// Sensors — battlescanner and friends' scanRange tech effect (Brian's ask,
|
||||||
|
// 2026-08-14). Unlike reachableStars/fuelRange, this isn't cached per turn:
|
||||||
|
// it is only ever asked for on a fleet click, not in a hot AI loop, so the
|
||||||
|
// extra cache bookkeeping (and the extra invalidation sites that would need
|
||||||
|
// it) buys nothing here.
|
||||||
|
|
||||||
|
// A fleet's current position in star-map pixel coordinates: its own star's,
|
||||||
|
// if docked, or interpolated along its lane if in transit — the same lerp
|
||||||
|
// VegaStarMap.js's refreshFleets uses to DRAW an in-transit marker, just
|
||||||
|
// reused here (headless, no Phaser) to MEASURE distance to it instead.
|
||||||
|
function fleetPosition(state, fleet) {
|
||||||
|
if (fleet.starIdx >= 0) {
|
||||||
|
const star = state.galaxy.stars[fleet.starIdx];
|
||||||
|
return star ? { x: star.x, y: star.y } : null;
|
||||||
|
}
|
||||||
|
const a = state.galaxy.stars[fleet.fromStar];
|
||||||
|
const b = state.galaxy.stars[fleet.toStar];
|
||||||
|
if (!a || !b) return null;
|
||||||
|
const t = fleet.total > 0 ? Math.min(1, Math.max(0, fleet.progress / fleet.total)) : 0;
|
||||||
|
return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every point this empire could currently be detecting FROM: its colonies
|
||||||
|
// and its own fleets (docked or in transit), unlike reachableStars' sources
|
||||||
|
// (colonies + starbases only) — a sensor reach is about where your hulls
|
||||||
|
// actually ARE right now, not where you could send something from.
|
||||||
|
function scanSources(state, e) {
|
||||||
|
const sources = [];
|
||||||
|
for (const c of empireColonies(state, e)) {
|
||||||
|
const star = state.galaxy.stars[c.starIdx];
|
||||||
|
if (star) sources.push({ x: star.x, y: star.y });
|
||||||
|
}
|
||||||
|
for (const f of empireFleets(state, e)) {
|
||||||
|
const p = fleetPosition(state, f);
|
||||||
|
if (p) sources.push(p);
|
||||||
|
}
|
||||||
|
return sources;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether `viewerE` can currently detect `fleet` (any empire's, including
|
||||||
|
* its own — always true then via a zero-distance source) on sensors: within
|
||||||
|
* scanRange parsecs of one of the viewer's own colonies or fleets. Zero
|
||||||
|
* scanRange (no battlescanner-line tech yet) means nothing is ever
|
||||||
|
* detected — this is what turns "reveals enemy fleet composition" from
|
||||||
|
* flavor text into a real mechanic (see VegaTechEffects.js's scanRange
|
||||||
|
* formatter, and MasterOfVegaGame.js's onFleetClick / VegaSidePanel.js's
|
||||||
|
* buildFleet for what unlocks behind it).
|
||||||
|
*/
|
||||||
|
export function fleetInScanRange(rules, state, viewerE, fleet) {
|
||||||
|
const range = empireComponents(rules, state, viewerE).scanRange;
|
||||||
|
if (range <= 0) return false;
|
||||||
|
const pos = fleetPosition(state, fleet);
|
||||||
|
if (!pos) return false;
|
||||||
|
return scanSources(state, viewerE).some(
|
||||||
|
(s) => Math.hypot(s.x - pos.x, s.y - pos.y) / PARSEC_PX <= range + 1e-9,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function fleetSpeed(rules, state, fleet) {
|
export function fleetSpeed(rules, state, fleet) {
|
||||||
const emp = state.empires[fleet.empireIdx];
|
const emp = state.empires[fleet.empireIdx];
|
||||||
const spec = rules.species[emp.speciesId];
|
const spec = rules.species[emp.speciesId];
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import {
|
||||||
coloniesAt, fleetsAt, empireDesign, fleetPower, fleetSpeed, fleetEta, etaTo,
|
coloniesAt, fleetsAt, empireDesign, fleetPower, fleetSpeed, fleetEta, etaTo,
|
||||||
colonyMaxPop, colonyProduction, colonyFactoryCap, effectiveFactories,
|
colonyMaxPop, colonyProduction, colonyFactoryCap, effectiveFactories,
|
||||||
colonyDefenseCap, habitableForEmpire, reachableStars, atWar, queueItemEta,
|
colonyDefenseCap, habitableForEmpire, reachableStars, atWar, queueItemEta,
|
||||||
|
fleetInScanRange,
|
||||||
} from './VegaLogic.js';
|
} from './VegaLogic.js';
|
||||||
import { FONT, D, ORBIT, uiClick } from './VegaScreens.js';
|
import { FONT, D, ORBIT, uiClick } from './VegaScreens.js';
|
||||||
import { playSound, SFX } from '../../ui/Sounds.js';
|
import { playSound, SFX } from '../../ui/Sounds.js';
|
||||||
|
|
@ -143,7 +144,11 @@ export default class VegaSidePanel {
|
||||||
// A fresh selection always starts at full strength: any dial-down from a
|
// A fresh selection always starts at full strength: any dial-down from a
|
||||||
// prior selection (this fleet or another) must not carry over here.
|
// prior selection (this fleet or another) must not carry over here.
|
||||||
this.sel = [];
|
this.sel = [];
|
||||||
this.syncSelection();
|
// The dial-down selector only exists for a fleet we can actually give
|
||||||
|
// orders to — a rival's fleet reaches here read-only, detected on
|
||||||
|
// sensors (MasterOfVegaGame.js's onFleetClick), and buildFleet's
|
||||||
|
// read-only branch never looks at this.sel at all.
|
||||||
|
if (fleet.empireIdx === this.viewerIdx) this.syncSelection();
|
||||||
this.rebuild();
|
this.rebuild();
|
||||||
this.open();
|
this.open();
|
||||||
}
|
}
|
||||||
|
|
@ -536,14 +541,20 @@ export default class VegaSidePanel {
|
||||||
const { rules, state } = this;
|
const { rules, state } = this;
|
||||||
const emp = state.empires[fleet.empireIdx];
|
const emp = state.empires[fleet.empireIdx];
|
||||||
const mine = fleet.empireIdx === this.viewerIdx;
|
const mine = fleet.empireIdx === this.viewerIdx;
|
||||||
|
// A rival fleet only opens into the read-only detail view once it's
|
||||||
|
// within sensor range — same gate MasterOfVegaGame.js's onFleetClick
|
||||||
|
// applies to a click on the map marker, mirrored here so this row is
|
||||||
|
// just as clickable (Brian's ask, 2026-08-14).
|
||||||
|
const scanned = mine || (this.viewerIdx >= 0 && fleetInScanRange(rules, state, this.viewerIdx, fleet));
|
||||||
const ships = fleet.ships.reduce((t, s) => t + s.count, 0);
|
const ships = fleet.ships.reduce((t, s) => t + s.count, 0);
|
||||||
const label = `${mine ? 'Your fleet' : emp.name} — ${ships} ship${ships === 1 ? '' : 's'}`
|
const label = `${mine ? 'Your fleet' : emp.name} — ${ships} ship${ships === 1 ? '' : 's'}`
|
||||||
+ ` · power ${fleetPower(rules, state, fleet)}`;
|
+ ` · power ${fleetPower(rules, state, fleet)}`;
|
||||||
const t = this.line(label, { size: 14, color: mine ? '#9fd8ff' : emp.color, gap: 3 });
|
const t = this.line(label, { size: 14, color: mine ? '#9fd8ff' : emp.color, gap: 3 });
|
||||||
if (!mine) return;
|
if (!scanned) return;
|
||||||
t.setInteractive({ useHandCursor: true });
|
t.setInteractive({ useHandCursor: true });
|
||||||
|
const baseColor = mine ? '#9fd8ff' : emp.color;
|
||||||
t.on('pointerover', () => t.setColor('#ffffff'));
|
t.on('pointerover', () => t.setColor('#ffffff'));
|
||||||
t.on('pointerout', () => t.setColor('#9fd8ff'));
|
t.on('pointerout', () => t.setColor(baseColor));
|
||||||
t.on('pointerup', () => this.cb.onSelectFleet?.(fleet));
|
t.on('pointerup', () => this.cb.onSelectFleet?.(fleet));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -589,6 +600,8 @@ export default class VegaSidePanel {
|
||||||
const fleet = this.fleet;
|
const fleet = this.fleet;
|
||||||
const total = fleet.ships.reduce((t, s) => t + s.count, 0);
|
const total = fleet.ships.reduce((t, s) => t + s.count, 0);
|
||||||
|
|
||||||
|
if (fleet.empireIdx !== this.viewerIdx) { this.buildEnemyFleet(total); return; }
|
||||||
|
|
||||||
if (fleet.starIdx < 0) {
|
if (fleet.starIdx < 0) {
|
||||||
const to = state.galaxy.stars[fleet.toStar];
|
const to = state.galaxy.stars[fleet.toStar];
|
||||||
const from = state.galaxy.stars[fleet.fromStar];
|
const from = state.galaxy.stars[fleet.fromStar];
|
||||||
|
|
@ -641,6 +654,58 @@ export default class VegaSidePanel {
|
||||||
this.action('Done', () => this.cb.onClose?.(), { variant: 'ghost' });
|
this.action('Done', () => this.cb.onClose?.(), { variant: 'ghost' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read-only view of a rival's fleet — reachable only once it is within
|
||||||
|
* sensor range (VegaLogic.js's fleetInScanRange, gated further up the call
|
||||||
|
* chain in MasterOfVegaGame.js's onFleetClick; Brian's ask, 2026-08-14).
|
||||||
|
* Reuses the exact same building blocks the player's own fleet view does —
|
||||||
|
* shipLine()/leaderRow()/detailHit() — for ship thumbnails, full-stat
|
||||||
|
* popovers and leader detail, so a rival's ships and captain inspect
|
||||||
|
* exactly like your own. What's missing is only the −/+/✕ dial-down
|
||||||
|
* selector and the move-order flow: there is nothing here to command.
|
||||||
|
*/
|
||||||
|
buildEnemyFleet(total) {
|
||||||
|
const { rules, state } = this;
|
||||||
|
const fleet = this.fleet;
|
||||||
|
const emp = state.empires[fleet.empireIdx];
|
||||||
|
|
||||||
|
if (fleet.starIdx < 0) {
|
||||||
|
const to = state.galaxy.stars[fleet.toStar];
|
||||||
|
const from = state.galaxy.stars[fleet.fromStar];
|
||||||
|
this.setHead(`${emp.name} fleet`, 'Under way — detected');
|
||||||
|
this.line(`${from?.name ?? '?'} → ${to?.name ?? '?'}`, { size: 17, color: emp.color });
|
||||||
|
} else {
|
||||||
|
const star = state.galaxy.stars[fleet.starIdx];
|
||||||
|
this.setHead(`${emp.name} fleet`, `In orbit at ${star.name} — detected`);
|
||||||
|
}
|
||||||
|
this.line(`${total} ship${total === 1 ? '' : 's'} · power ${fleetPower(rules, state, fleet)}`,
|
||||||
|
{ size: 15, color: '#9fb6cc' });
|
||||||
|
this.leaderRow();
|
||||||
|
|
||||||
|
const mobile = fleet.ships.filter((s) => s.count > 0
|
||||||
|
&& !empireDesign(rules, state, fleet.empireIdx, s.hullId).immobile);
|
||||||
|
const garrison = fleet.ships.filter((s) => s.count > 0
|
||||||
|
&& empireDesign(rules, state, fleet.empireIdx, s.hullId).immobile);
|
||||||
|
if (mobile.length) {
|
||||||
|
this.heading('Ships');
|
||||||
|
for (const s of mobile) {
|
||||||
|
this.shipLine(fleet.empireIdx, s.hullId, s.mark, `${s.count} × ${this.designName(s.hullId, s.mark)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (garrison.length) {
|
||||||
|
this.heading('Garrison');
|
||||||
|
for (const s of garrison) {
|
||||||
|
this.shipLine(fleet.empireIdx, s.hullId, s.mark,
|
||||||
|
`${s.count} × ${this.designName(s.hullId, s.mark)}`,
|
||||||
|
{ color: '#8fa8c0', sub: 'Holds this system' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.y = Math.max(this.y + 12, this.h - 138);
|
||||||
|
this.callout('Detected on sensors — read-only. Click a ship or the captain to inspect it.', '#6fc4ff');
|
||||||
|
this.action('Close', () => this.cb.onClose?.(), { variant: 'ghost' });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A ship stack with a −/+ count selector. The name sits on the first line and
|
* A ship stack with a −/+ count selector. The name sits on the first line and
|
||||||
* the counter on the second, so the thumbnails can take the left gutter
|
* the counter on the second, so the thumbnails can take the left gutter
|
||||||
|
|
@ -732,9 +797,11 @@ export default class VegaSidePanel {
|
||||||
const rowH = Math.max(SIZE, this.y - rowY);
|
const rowH = Math.max(SIZE, this.y - rowY);
|
||||||
this.y = Math.max(this.y, rowY + SIZE + 10);
|
this.y = Math.max(this.y, rowY + SIZE + 10);
|
||||||
|
|
||||||
|
const mine = fleet.empireIdx === this.viewerIdx;
|
||||||
|
const whose = mine ? 'Your fleet' : `${state.empires[fleet.empireIdx].name}'s fleet`;
|
||||||
const where = fleet.starIdx >= 0
|
const where = fleet.starIdx >= 0
|
||||||
? `Your fleet at ${state.galaxy.stars[fleet.starIdx]?.name ?? '?'}`
|
? `${whose} at ${state.galaxy.stars[fleet.starIdx]?.name ?? '?'}`
|
||||||
: `Your fleet, en route to ${state.galaxy.stars[fleet.toStar]?.name ?? '?'}`;
|
: `${whose}, en route to ${state.galaxy.stars[fleet.toStar]?.name ?? '?'}`;
|
||||||
const hit = this.scene.add.rectangle(PAD, rowY, COL, rowH, 0xffffff, 0.001)
|
const hit = this.scene.add.rectangle(PAD, rowY, COL, rowH, 0xffffff, 0.001)
|
||||||
.setOrigin(0, 0).setInteractive({ useHandCursor: true });
|
.setOrigin(0, 0).setInteractive({ useHandCursor: true });
|
||||||
hit.on('pointerup', () => this.openLeaderDetailFor(leader, where));
|
hit.on('pointerup', () => this.openLeaderDetailFor(leader, where));
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,11 @@
|
||||||
//
|
//
|
||||||
// Every formatter is hand-matched to how the effect is actually consumed
|
// Every formatter is hand-matched to how the effect is actually consumed
|
||||||
// elsewhere in the engine (VegaCombat.js/VegaLogic.js/VegaShips.js) — not a
|
// elsewhere in the engine (VegaCombat.js/VegaLogic.js/VegaShips.js) — not a
|
||||||
// generic key/value dump. `scanRange` is currently an inert stat with no
|
// generic key/value dump. `scanRange` (VegaLogic.js's fleetInScanRange) is
|
||||||
// downstream mechanic wired up yet; its wording stays modest/flavor rather
|
// what unlocks a rival fleet's read-only detail view — MasterOfVegaGame.js's
|
||||||
// than promising a numeric benefit that doesn't exist in the sim.
|
// onFleetClick and VegaSidePanel.js's buildFleet — once one of your own
|
||||||
|
// colonies or fleets sits within range of it; it does not reveal star
|
||||||
|
// systems themselves (that's explored[], unrelated).
|
||||||
|
|
||||||
const pct = (mult) => `${Math.round(Math.abs(mult - 1) * 100)}%`;
|
const pct = (mult) => `${Math.round(Math.abs(mult - 1) * 100)}%`;
|
||||||
|
|
||||||
|
|
@ -46,7 +48,8 @@ const FORMATTERS = {
|
||||||
refitCostMult: (rules, v) => `Auto-refit costs reduced by ${pct(v)}.`,
|
refitCostMult: (rules, v) => `Auto-refit costs reduced by ${pct(v)}.`,
|
||||||
repairPerRound: (rules, v) => `Ships self-repair ${Math.round(v * 100)}% of max hull every combat round.`,
|
repairPerRound: (rules, v) => `Ships self-repair ${Math.round(v * 100)}% of max hull every combat round.`,
|
||||||
researchMult: (rules, v) => `Research output increased by ${pct(v)} empire-wide.`,
|
researchMult: (rules, v) => `Research output increased by ${pct(v)} empire-wide.`,
|
||||||
scanRange: (rules, v) => `+${v} scanner range for detecting distant fleets and systems.`,
|
scanRange: (rules, v) => `+${v} parsec scanner range — enemy fleets within range of one of your `
|
||||||
|
+ 'colonies or fleets can be inspected in full, ship by ship.',
|
||||||
shield: (rules, v) => `Deflector shields absorb ${v} point${v === 1 ? '' : 's'} of damage from every incoming hit, on ships and planetary defenses alike.`,
|
shield: (rules, v) => `Deflector shields absorb ${v} point${v === 1 ? '' : 's'} of damage from every incoming hit, on ships and planetary defenses alike.`,
|
||||||
singularity: (rules) => `A breakthrough in exotic physics — every weapon on this ship warps enemy deflectors, trimming ${rules.combat.singularityShieldPierce ?? 0} point of shielding off every hit it lands.`,
|
singularity: (rules) => `A breakthrough in exotic physics — every weapon on this ship warps enemy deflectors, trimming ${rules.combat.singularityShieldPierce ?? 0} point of shielding off every hit it lands.`,
|
||||||
targeting: (rules, v) => `Combat accuracy increased by ${Math.round(v * 6)}% (better targeting computers).`,
|
targeting: (rules, v) => `Combat accuracy increased by ${Math.round(v * 6)}% (better targeting computers).`,
|
||||||
|
|
|
||||||
|
|
@ -1474,6 +1474,68 @@ section('4c. Population transport');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
section('4d. Sensor scan range');
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// fleetInScanRange: Battle Scanner and friends' scanRange tech effect,
|
||||||
|
// wired from flavor text into an actual mechanic (Brian's ask, 2026-08-14).
|
||||||
|
{
|
||||||
|
const st = Logic.createGame(RULES, {
|
||||||
|
sizeId: 'medium', shapeId: 'spiral', seed: 44, difficultyId: 'normal',
|
||||||
|
speciesIds: ['human', 'kkrix'], humanIndex: 0,
|
||||||
|
});
|
||||||
|
st.rules = RULES;
|
||||||
|
const home = st.colonies.find((c) => c.empireIdx === 0);
|
||||||
|
|
||||||
|
Logic.addFleet(RULES, st, 1, home.starIdx, [{ hullId: 'frigate', count: 2, mark: 1 }]);
|
||||||
|
const rival = st.fleets.find((f) => f.empireIdx === 1 && f.starIdx === home.starIdx);
|
||||||
|
check('a rival fleet exists to test against', !!rival);
|
||||||
|
|
||||||
|
check('with no scanner tech, even a same-system rival fleet is undetected',
|
||||||
|
!Logic.fleetInScanRange(RULES, st, 0, rival));
|
||||||
|
|
||||||
|
Logic.grantTech(RULES, st, 0, 'battlescanner');
|
||||||
|
check('battlescanner grants a positive empire scanRange',
|
||||||
|
Logic.empireComponents(RULES, st, 0).scanRange > 0);
|
||||||
|
check('with battlescanner known, a same-system rival fleet is detected',
|
||||||
|
Logic.fleetInScanRange(RULES, st, 0, rival));
|
||||||
|
|
||||||
|
// Far enough that even battlescanner's scan bonus can't reach it.
|
||||||
|
let farIdx = -1;
|
||||||
|
let farDist = 0;
|
||||||
|
for (let i = 0; i < st.galaxy.stars.length; i += 1) {
|
||||||
|
const d = parsecs(st.galaxy, home.starIdx, i);
|
||||||
|
if (d > farDist) { farDist = d; farIdx = i; }
|
||||||
|
}
|
||||||
|
const scanRange = Logic.empireComponents(RULES, st, 0).scanRange;
|
||||||
|
check('a star far enough away to test the range limit exists', farIdx >= 0 && farDist > scanRange);
|
||||||
|
if (farIdx >= 0 && farDist > scanRange) {
|
||||||
|
rival.starIdx = farIdx;
|
||||||
|
check('a rival fleet beyond scan range is not detected even with battlescanner',
|
||||||
|
!Logic.fleetInScanRange(RULES, st, 0, rival));
|
||||||
|
|
||||||
|
// Scan sources are the viewer's own fleets too, not just colonies —
|
||||||
|
// sending one of ours next to the rival should light it up.
|
||||||
|
Logic.addFleet(RULES, st, 0, farIdx, [{ hullId: 'scout', count: 1, mark: 1 }]);
|
||||||
|
check('a rival fleet becomes detected once one of our OWN fleets sits at its star',
|
||||||
|
Logic.fleetInScanRange(RULES, st, 0, rival));
|
||||||
|
|
||||||
|
// In-transit detection uses the same interpolated position VegaStarMap.js
|
||||||
|
// draws an en-route marker at, not just docked fleets.
|
||||||
|
const transit = {
|
||||||
|
id: st.nextFleetId += 1, empireIdx: 1, starIdx: -1,
|
||||||
|
fromStar: home.starIdx, toStar: farIdx, progress: 0, total: farDist,
|
||||||
|
ships: [{ hullId: 'frigate', mark: 1, count: 1 }],
|
||||||
|
};
|
||||||
|
st.fleets.push(transit);
|
||||||
|
check('an in-transit rival fleet just leaving our home star is detected',
|
||||||
|
Logic.fleetInScanRange(RULES, st, 0, transit));
|
||||||
|
transit.progress = farDist * 0.5;
|
||||||
|
check('the same in-transit rival fleet, well down its route, is no longer detected',
|
||||||
|
!Logic.fleetInScanRange(RULES, st, 0, transit));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
section('5. Combat');
|
section('5. Combat');
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue