Spaces:
Paused
Paused
File size: 3,814 Bytes
7e764b0 c310233 f71d721 c310233 f71d721 c310233 7e764b0 c310233 e847942 c310233 f71d721 c310233 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | // @ts-check
/* ============================================================
Autonomous scout mode for the Ranger (order type "scout"): range the
map on its own, always heading for the nearest unexplored ground, so
one click sends a scout off to chart the map instead of the player
hand-walking it waypoint by waypoint.
It heads for the nearest never-explored cell (its own owner's fog). Its
long sight reveals that ground as it approaches, so the target flips to
explored and it re-picks the next-nearest dark cell β the effect is a
frontier that expands outward from home until the map is charted. Once
nothing is left unexplored it patrols a fixed circuit of the map's
quadrants, keeping vision fresh rather than freezing in place.
Persistent: the "scout" order is never cleared by arrival (there's no
single destination), so it keeps exploring until the player re-orders the
unit (a move/attack/stop replaces it). Deterministic throughout β the
target comes from a row-major fog scan, no rng β so it's safe in the sim.
A Ranger LEADING a formation (engine/commands.js issueScout) picks the same frontier targets
but travels at the group's pace (order.speedCap) rather than its own full speed, so its
escorts β still on their ordinary follow-leader order β can keep up: a protective scout
leading the pack into the fog, not a lone unit racing off alone.
============================================================ */
"use strict";
import { stepToward, orderedSpeed } from "./movement.js";
import { UNITS } from "./entities.js";
import { nearestUnexploredPoint, isExploredAt } from "./fog.js";
const REACH = 6; // close enough to a scout waypoint to count as arrived and re-pick
// A coarse patrol circuit for when the whole map is already charted: the four
// quadrant centres, walked in a loop so the scout keeps sweeping for fresh
// intel (enemy movements) instead of parking. Fractions of the map dims.
const PATROL = [[0.25, 0.25], [0.75, 0.25], [0.75, 0.75], [0.25, 0.75]];
/** @param {State} state @param {Unit} unit @param {number} dt @returns {void} */
export function updateScoutMode(state, unit, dt) {
const def = UNITS[unit.type];
const fog = unit.owner === "player" ? state.fog : state.fogAI;
const order = unit.order;
// Re-pick a target when we have none, we've reached it, or our own sight has
// since revealed it (so we always chase ground that's still dark).
const need = order.tx == null
|| Math.hypot(order.tx - unit.x, order.ty - unit.y) <= REACH
|| (fog && order.explore && isExploredAt(fog, order.tx, order.ty));
if (need) {
const spot = fog ? nearestUnexploredPoint(fog, unit.x, unit.y) : null;
if (spot) {
order.tx = spot.x; order.ty = spot.y; order.explore = true;
} else {
// Nothing left to discover β walk a patrol circuit to keep vision fresh.
// `patrolLeg`, NOT `patrol`: engine/commands.js issuePatrol stamps `patrol: true` as a boolean
// "requeue me" flag that engine/sim.js reads off orderQueue, so storing a circuit INDEX under
// the same name is a shape-dependent collision β leg 0 is falsy (no requeue), legs 1-3 are
// truthy (infinite requeue of the same order object). Latent only because issueScout writes
// u.order directly and clears orderQueue; both commands are gated to role === "scout", so the
// Ranger is the one unit that can hold either kind of order.
order.patrolLeg = ((order.patrolLeg ?? -1) + 1) % PATROL.length;
const [fx, fy] = PATROL[order.patrolLeg];
order.tx = (state.map?.width || 0) * fx;
order.ty = (state.map?.height || 0) * fy;
order.explore = false;
}
}
stepToward(state, unit, order.tx, order.ty, orderedSpeed(def.speed, order), dt);
}
|