Enforce on-screen rule for combat engagement and hits
- Add inView() pure geometry helper to test if a craft's visual circle is at least partly within the camera's visible rect - Gate enemy fire, auto-fire target selection, currentTarget lock, and bullet collision checks behind craftVisible() so off-screen hostiles hold lances, can't be engaged, and are immune to tracers until they fly into view - Extend pickTarget() with an optional qualifies predicate to narrow the candidate pool (used for the on-screen rule) - Document the rule in data/combat.json and CombatSystem class comments
This commit is contained in:
parent
d61f2d6934
commit
5890e93cde
|
|
@ -3,7 +3,7 @@
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
|
||||||
"game": {
|
"game": {
|
||||||
"_comment": "COMBAT IN THE MAIN GAME (the ?combat boot). spawnCount = hostiles that appear around the ship when the player starts. SPAWNING: hostiles materialize OFF-SCREEN — the furthest of the (spawnRadius band, the camera's visible edge + spawnLead) — and FLY IN on their own chase AI (standoff ring, then lances): nothing pops into view mid-fight. spawnRadius px = the minimum materialization band around the ship; spawnLead px = how far PAST the camera's visible edge they materialize (the fly-in distance — bigger = a longer stream across the screen). autoFire = the ship's guns — while a hostile is within range the ship fires at the NEAREST one (the mouse steers, the cannon tracks; the cannon's own numbers are player.weapon below).",
|
"_comment": "COMBAT IN THE MAIN GAME (the ?combat boot). spawnCount = hostiles that appear around the ship when the player starts. SPAWNING: hostiles materialize OFF-SCREEN — the furthest of the (spawnRadius band, the camera's visible edge + spawnLead) — and FLY IN on their own chase AI (standoff ring, then lances): nothing pops into view mid-fight. spawnRadius px = the minimum materialization band around the ship; spawnLead px = how far PAST the camera's visible edge they materialize (the fly-in distance — bigger = a longer stream across the screen). autoFire = the ship's guns — while a hostile is within range the ship fires at the NEAREST one (the mouse steers, the cannon tracks; the cannon's own numbers are player.weapon below). ON-SCREEN RULE (js/combat/CombatSystem.js): a hostile must be VISUALLY ON SCREEN (at least partly in the camera's view) to fire or be fired upon — off-screen craft hold their lances, the cannon won't engage them, and their tracers pass straight through; they join the fight the moment they fly into view.",
|
||||||
"spawnCount": 3,
|
"spawnCount": 3,
|
||||||
"spawnRadius": 340,
|
"spawnRadius": 340,
|
||||||
"spawnLead": 140,
|
"spawnLead": 140,
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@
|
||||||
* standoff-ring AI, the lance decision), the tracer (Bullet.js:
|
* standoff-ring AI, the lance decision), the tracer (Bullet.js:
|
||||||
* velocity from angle/speed, world radius, one-shot consume, life
|
* velocity from angle/speed, world radius, one-shot consume, life
|
||||||
* expiry), and the system (CombatSystem.js: the pack's lifecycle,
|
* expiry), and the system (CombatSystem.js: the pack's lifecycle,
|
||||||
* the auto-fire, swept hits, rams, the player's armor + respawn,
|
* the auto-fire, the on-screen rule, swept hits, rams, the player's
|
||||||
* the energy) — then asserts the behavior the range
|
* armor + respawn, the energy) — then asserts the behavior the range
|
||||||
* (js/combat/CombatScene.js) and the main game (GameScene) are built on.
|
* (js/combat/CombatScene.js) and the main game (GameScene) are built on.
|
||||||
*
|
*
|
||||||
* All numbers come from the real data/*.json (data/combat.json included),
|
* All numbers come from the real data/*.json (data/combat.json included),
|
||||||
|
|
@ -166,7 +166,7 @@ for (const f of fs.readdirSync(dataDir)) {
|
||||||
config.init(configData);
|
config.init(configData);
|
||||||
console.log('config loaded from data/:', Object.keys(configData).join(', '));
|
console.log('config loaded from data/:', Object.keys(configData).join(', '));
|
||||||
|
|
||||||
const { applyDamage, headingOf, orbitStep, handleFeel, pickTarget, feedPush, offscreenArrow } = await import(
|
const { applyDamage, headingOf, orbitStep, handleFeel, pickTarget, feedPush, offscreenArrow, inView } = await import(
|
||||||
pathToFileURL(join(__dirname, '../js/combat/Combat.js')).href
|
pathToFileURL(join(__dirname, '../js/combat/Combat.js')).href
|
||||||
);
|
);
|
||||||
const { EnemyShip } = await import(pathToFileURL(join(__dirname, '../js/combat/EnemyShip.js')).href);
|
const { EnemyShip } = await import(pathToFileURL(join(__dirname, '../js/combat/EnemyShip.js')).href);
|
||||||
|
|
@ -529,6 +529,72 @@ const E = (k, d) => config.get(`combat.enemy.${k}`, d);
|
||||||
check('spawn: destroy still clears everything', combat.enemies.length === 0);
|
check('spawn: destroy still clears everything', combat.enemies.length === 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------- the on-screen rule
|
||||||
|
|
||||||
|
{
|
||||||
|
// ON-SCREEN RULE (the main game): a hostile must be at least PARTLY
|
||||||
|
// in the camera's view to fire or be fired upon — off-screen craft
|
||||||
|
// hold their lances, the cannon won't engage them, and their tracers
|
||||||
|
// pass through (they re-enter the fight as they fly in).
|
||||||
|
const VW = 1280, VH = 720;
|
||||||
|
const scene = makeScene({ withSheet: true });
|
||||||
|
scene.cameras = { main: { worldView: { x: 0, y: 0, width: VW, height: VH } } };
|
||||||
|
const ship = scene.physics.add.existing(new PhaserStub.Physics.Arcade.Sprite(scene, 640, 360, '__ship', 0));
|
||||||
|
ship.radius = 12;
|
||||||
|
ship.stop = () => {};
|
||||||
|
const combat = new CombatSystem(scene, { ship, onKill: () => {} });
|
||||||
|
|
||||||
|
// One on-screen (below the player), one off-screen (above the top
|
||||||
|
// edge) — both within lance range (560), the off-screen one NEARER,
|
||||||
|
// so "nearest" alone would pick the wrong one.
|
||||||
|
const on = combat.spawnEnemy(640, 600, 0);
|
||||||
|
const off = combat.spawnEnemy(640, -50, 1);
|
||||||
|
const aim = (e) => {
|
||||||
|
e.rotation = Math.atan2(ship.y - e.y, ship.x - e.x) + e.artOffset;
|
||||||
|
e.nextFireAt = 0; // lift the spawn grace — armed for this frame
|
||||||
|
};
|
||||||
|
aim(on);
|
||||||
|
aim(off);
|
||||||
|
|
||||||
|
check('view: the on-screen craft is visible, the off-screen one is not',
|
||||||
|
combat.craftVisible(on) === true && combat.craftVisible(off) === false);
|
||||||
|
|
||||||
|
// ENEMY FIRE: the on-screen one lances; the off-screen one holds.
|
||||||
|
combat.update(1000, 16);
|
||||||
|
const eShots = combat.bullets.filter((b) => b.owner === 'enemy');
|
||||||
|
check('view: an off-screen craft holds its fire (no lance born off-screen)',
|
||||||
|
eShots.length >= 1 && eShots.every((b) => b.y > 0));
|
||||||
|
check('view: the on-screen craft lances (its shot is born in view)',
|
||||||
|
eShots.some((b) => b.y > 0 && b.y < 600));
|
||||||
|
|
||||||
|
// AUTO-FIRE + LOCK: the on-screen hostile is engaged even though the
|
||||||
|
// off-screen one is NEARER (the gun and the brackets track the same).
|
||||||
|
const pShots = combat.bullets.filter((b) => b.owner === 'player');
|
||||||
|
check('view: auto-fire targets the on-screen hostile (not the nearer off-screen one)',
|
||||||
|
pShots.length >= 1 && pShots.every((b) => b.y > 360));
|
||||||
|
check('view: currentTarget = the on-screen hostile (the lock follows the guns)',
|
||||||
|
combat.currentTarget() === on);
|
||||||
|
|
||||||
|
// BE HIT: a tracer on the off-screen hostile's path passes straight
|
||||||
|
// through (it cannot be shot while off-screen) …
|
||||||
|
const clearAll = () => { for (const b of combat.bullets) b.consume(); combat.bullets.length = 0; };
|
||||||
|
clearAll();
|
||||||
|
const offArmor = { hull: off.hull, shields: off.shields };
|
||||||
|
combat.spawnBullet(off.x - 18, off.y, 0, { bulletSpeed: 500, damage: 999, life: 2, color: '#fff', size: 16 }, 'player');
|
||||||
|
combat.update(1016, 16);
|
||||||
|
check('view: an off-screen hostile takes no hits (the tracer passes through)',
|
||||||
|
off.dead === false && off.hull === offArmor.hull && off.shields === offArmor.shields);
|
||||||
|
|
||||||
|
// …and the same path on the on-screen one connects (killing over the hull).
|
||||||
|
clearAll();
|
||||||
|
combat.spawnBullet(on.x - 18, on.y, 0, { bulletSpeed: 500, damage: 999, life: 2, color: '#fff', size: 16 }, 'player');
|
||||||
|
combat.update(1032, 16);
|
||||||
|
check('view: an on-screen hostile takes the hit (and goes down)', on.dead === true && combat.kills === 1);
|
||||||
|
|
||||||
|
combat.destroy();
|
||||||
|
check('view: destroy still clears everything', combat.enemies.length === 0 && combat.bullets.length === 0);
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------ SFX
|
// ------------------------------------------------------------------ SFX
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
@ -627,6 +693,24 @@ const E = (k, d) => config.get(`combat.enemy.${k}`, d);
|
||||||
check('read: pickTarget empty / all-dead → null',
|
check('read: pickTarget empty / all-dead → null',
|
||||||
pickTarget([], 0, 0) === null && pickTarget([B], 0, 0) === null);
|
pickTarget([], 0, 0) === null && pickTarget([B], 0, 0) === null);
|
||||||
check('read: pickTarget tolerates missing entries', pickTarget([null, undefined, A], 0, 0) === A);
|
check('read: pickTarget tolerates missing entries', pickTarget([null, undefined, A], 0, 0) === A);
|
||||||
|
check('read: pickTarget honors the qualifies predicate (the on-screen seam)',
|
||||||
|
pickTarget([A, C], 0, 0, Infinity, (e) => e.x < 50) === A &&
|
||||||
|
pickTarget([C], 0, 0, Infinity, (e) => e.x < 50) === null);
|
||||||
|
|
||||||
|
// inView — the ON-SCREEN RULE's pure geometry (a craft's visual
|
||||||
|
// circle × the camera's visible rect): a craft is in view while ANY
|
||||||
|
// part of it is; it is off-screen only once fully out. A null/zero
|
||||||
|
// view (headless) = no constraint.
|
||||||
|
const view = { x: 0, y: 0, width: 1280, height: 720 };
|
||||||
|
check('view: fully inside → in view', inView(640, 360, 20, view) === true);
|
||||||
|
check('view: half off the edge → still in view (still visible)',
|
||||||
|
inView(1270, 360, 20, view) === true && inView(640, -10, 20, view) === true);
|
||||||
|
check('view: fully off the edge → out of view',
|
||||||
|
inView(1310, 360, 20, view) === false && inView(640, -100, 20, view) === false);
|
||||||
|
check('view: corner contact counts (the circle touches the rect)',
|
||||||
|
inView(1290, 710, 20, view) === true && inView(1305, 730, 10, view) === false);
|
||||||
|
check('view: no view (headless) → everything is in view',
|
||||||
|
inView(5000, 5000, 20, null) === true && inView(5000, 5000, 20, { width: 0, height: 0 }) === true);
|
||||||
|
|
||||||
// feedPush — the kill feed's pure state step.
|
// feedPush — the kill feed's pure state step.
|
||||||
const f1 = feedPush([], 'KILL A', 1000, 5, 3000);
|
const f1 = feedPush([], 'KILL A', 1000, 5, 3000);
|
||||||
|
|
|
||||||
|
|
@ -132,16 +132,21 @@ export function orbitStep(px, py, orbitAngle, orbitDir, orbitSpeed, dt, orbitRad
|
||||||
* The CURRENT TARGET (the one your guns are on): the nearest LIVE hostile
|
* The CURRENT TARGET (the one your guns are on): the nearest LIVE hostile
|
||||||
* to (x, y) within `range` px, or null. The range's lock indicator and the
|
* to (x, y) within `range` px, or null. The range's lock indicator and the
|
||||||
* main game's auto-fire both run this rule, so "what's locked" never
|
* main game's auto-fire both run this rule, so "what's locked" never
|
||||||
* drifts between the two. Dead hostiles never qualify.
|
* drifts between the two. Dead hostiles never qualify; a `qualifies`
|
||||||
|
* predicate (when given) narrows the pool further — the main game's
|
||||||
|
* auto-fire passes its on-screen rule (CombatSystem.craftVisible), so
|
||||||
|
* the guns only ever engage a hostile that's actually in view.
|
||||||
*
|
*
|
||||||
|
* @param {function(object):boolean} [qualifies] extra eligibility test (null = all live hostiles)
|
||||||
* @returns {object|null} the target (anything with .x/.y/.dead)
|
* @returns {object|null} the target (anything with .x/.y/.dead)
|
||||||
*/
|
*/
|
||||||
export function pickTarget(enemies, x, y, range = Infinity) {
|
export function pickTarget(enemies, x, y, range = Infinity, qualifies = null) {
|
||||||
let best = null;
|
let best = null;
|
||||||
let bestD = Infinity;
|
let bestD = Infinity;
|
||||||
const r2 = range * range;
|
const r2 = range * range;
|
||||||
for (const e of enemies || []) {
|
for (const e of enemies || []) {
|
||||||
if (!e || e.dead) continue;
|
if (!e || e.dead) continue;
|
||||||
|
if (qualifies && !qualifies(e)) continue;
|
||||||
const dx = e.x - x;
|
const dx = e.x - x;
|
||||||
const dy = e.y - y;
|
const dy = e.y - y;
|
||||||
const d2 = dx * dx + dy * dy;
|
const d2 = dx * dx + dy * dy;
|
||||||
|
|
@ -153,6 +158,37 @@ export function pickTarget(enemies, x, y, range = Infinity) {
|
||||||
return best;
|
return best;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ON-SCREEN RULE (pure): is a craft with a visual circle ((x, y),
|
||||||
|
* radius r) at least PARTIALLY inside the camera's visible world rect
|
||||||
|
* (`view` — {x, y, width, height})? A ship sliding off the edge is still
|
||||||
|
* visible — and still in the fight — while any part of it is on screen;
|
||||||
|
* it is "off-screen" only once fully out. A null/zero view (a scene
|
||||||
|
* without a camera — the headless tests) = no constraint: everything
|
||||||
|
* counts as on screen.
|
||||||
|
*
|
||||||
|
* This is the combat rule "a hostile must be VISUALLY ON SCREEN to fire
|
||||||
|
* or be fired upon" (js/combat/CombatSystem.js): off-screen hostiles
|
||||||
|
* hold their lances, the cannon won't engage them, and their tracers
|
||||||
|
* pass through. The scene-side craftVisible() feeds its camera's
|
||||||
|
* worldView in here.
|
||||||
|
*
|
||||||
|
* @param {number} x the craft's x (world px)
|
||||||
|
* @param {number} y the craft's y (world px)
|
||||||
|
* @param {number} r the craft's visual radius (world px)
|
||||||
|
* @param {object|null} view the camera's visible world rect (or null)
|
||||||
|
* @returns {boolean} true when the craft is (partly) on screen
|
||||||
|
*/
|
||||||
|
export function inView(x, y, r, view) {
|
||||||
|
if (!view || !(Number(view.width) > 0) || !(Number(view.height) > 0)) return true;
|
||||||
|
const rr = Math.max(0, Number(r) || 0);
|
||||||
|
const L = Number(view.x) || 0;
|
||||||
|
const T = Number(view.y) || 0;
|
||||||
|
const R = L + Number(view.width);
|
||||||
|
const B = T + Number(view.height);
|
||||||
|
return x + rr >= L && x - rr <= R && y + rr >= T && y - rr <= B;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* One kill-feed entry lands (pure state step): unshift {label, t: now},
|
* One kill-feed entry lands (pure state step): unshift {label, t: now},
|
||||||
* drop anything older than `life` ms, cap at `max` lines. Returns a NEW
|
* drop anything older than `life` ms, cap at `max` lines. Returns a NEW
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { EnemyShip } from './EnemyShip.js';
|
||||||
import { enemyTypeConfig } from './EnemyTypes.js';
|
import { enemyTypeConfig } from './EnemyTypes.js';
|
||||||
import { Bullet } from './Bullet.js';
|
import { Bullet } from './Bullet.js';
|
||||||
import { Fx, FX_COLORS, settleCraftTint } from './Fx.js';
|
import { Fx, FX_COLORS, settleCraftTint } from './Fx.js';
|
||||||
import { applyDamage, pickTarget } from './Combat.js';
|
import { applyDamage, inView, pickTarget } from './Combat.js';
|
||||||
import { combatSfx, combatSfxEnemyHit } from './CombatSfx.js';
|
import { combatSfx, combatSfxEnemyHit } from './CombatSfx.js';
|
||||||
import { combatShake } from './CamShake.js';
|
import { combatShake } from './CamShake.js';
|
||||||
import { normalizeWaves, WaveRunner } from './Waves.js';
|
import { normalizeWaves, WaveRunner } from './Waves.js';
|
||||||
|
|
@ -24,6 +24,11 @@ const TAU = Math.PI * 2;
|
||||||
* - the AUTO-FIRE (→ game.autoFire: while a hostile is in range, the
|
* - the AUTO-FIRE (→ game.autoFire: while a hostile is in range, the
|
||||||
* ship fires at the NEAREST one — the mouse steers,
|
* ship fires at the NEAREST one — the mouse steers,
|
||||||
* the cannon tracks; the cannon's numbers are → player.weapon)
|
* the cannon tracks; the cannon's numbers are → player.weapon)
|
||||||
|
* - the ON-SCREEN RULE (a hostile must be VISUALLY ON SCREEN to fire or
|
||||||
|
* be fired upon: an off-screen craft holds its lance,
|
||||||
|
* the cannon won't engage it, and its tracers pass
|
||||||
|
* through — it enters the fight only once it has
|
||||||
|
* flown into view; pure geometry in Combat.js → inView)
|
||||||
* - the collision pass (swept bullet hits + rams — the main game's
|
* - the collision pass (swept bullet hits + rams — the main game's
|
||||||
* hand-rolled circle-check style, no arcade colliders)
|
* hand-rolled circle-check style, no arcade colliders)
|
||||||
* - the WAVE RUNS (Waves.js state machine — quest-step combat: an
|
* - the WAVE RUNS (Waves.js state machine — quest-step combat: an
|
||||||
|
|
@ -161,6 +166,32 @@ export class CombatSystem {
|
||||||
return this.spawnRadius;
|
return this.spawnRadius;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The camera's visible world rect (world px, zoom included) — the
|
||||||
|
* surface the ON-SCREEN RULE tests against. Null when the scene has no
|
||||||
|
* camera (the headless tests): then nothing is off-screen and the rule
|
||||||
|
* is a no-op.
|
||||||
|
*/
|
||||||
|
visibleView() {
|
||||||
|
const cam = this.scene?.cameras?.main;
|
||||||
|
const v = cam && cam.worldView ? cam.worldView : null;
|
||||||
|
return v && v.width > 0 && v.height > 0 ? v : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ON-SCREEN RULE (the pure geometry is Combat.js → inView): a
|
||||||
|
* hostile is "on screen" while ANY part of its visual disc (its
|
||||||
|
* hitRadius, or half its hull length — whichever is wider) still
|
||||||
|
* touches the camera's visible rect. Off-screen hostiles are OUT of
|
||||||
|
* the fight: they hold their lances, the cannon won't engage them, and
|
||||||
|
* their tracers pass through (they re-enter the moment they fly in).
|
||||||
|
*/
|
||||||
|
craftVisible(e) {
|
||||||
|
if (!e) return false;
|
||||||
|
const r = Math.max(Number(e.hitRadius) || 0, (Number(e.size) || 0) * 0.5);
|
||||||
|
return inView(e.x, e.y, r, this.visibleView());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spawn the starting pack in a ring around (cx, cy) — the ?combat boot
|
* Spawn the starting pack in a ring around (cx, cy) — the ?combat boot
|
||||||
* calls this with the ship's position "when the player starts". The ring
|
* calls this with the ship's position "when the player starts". The ring
|
||||||
|
|
@ -365,27 +396,36 @@ export class CombatSystem {
|
||||||
const player = this.playerDown ? null : this.ship;
|
const player = this.playerDown ? null : this.ship;
|
||||||
|
|
||||||
// 1) The pack: orbit AI + lances (a dead one is its reform clock).
|
// 1) The pack: orbit AI + lances (a dead one is its reform clock).
|
||||||
|
// The ON-SCREEN RULE: the AI may WANT to fire, but the lance only
|
||||||
|
// leaves the barrel while the craft is (at least partly) on screen —
|
||||||
|
// an off-screen hostile holds its fire until it flies into view.
|
||||||
for (const e of this.enemies) {
|
for (const e of this.enemies) {
|
||||||
if (e.dead) {
|
if (e.dead) {
|
||||||
if (e._respawnPending && time >= e.respawnAt) this.reviveEnemy(e, time);
|
if (e._respawnPending && time >= e.respawnAt) this.reviveEnemy(e, time);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const wantsFire = e.update(time, delta, player);
|
const wantsFire = e.update(time, delta, player);
|
||||||
if (wantsFire && player) this.fireEnemy(e, player, time);
|
if (wantsFire && player && this.craftVisible(e)) this.fireEnemy(e, player, time);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) AUTO-FIRE: the nearest live hostile in range (game.autoFire).
|
// 2) AUTO-FIRE: the nearest VISIBLE hostile in range (game.autoFire).
|
||||||
// The mouse steers the ship; the cannon tracks on its own.
|
// The mouse steers the ship; the cannon tracks on its own — but
|
||||||
|
// only at what's on screen (off-screen hostiles can't be fired
|
||||||
|
// upon; the NEAREST counts only if it's in view).
|
||||||
if (
|
if (
|
||||||
player &&
|
player &&
|
||||||
this.autoFire.enabled &&
|
this.autoFire.enabled &&
|
||||||
this.autoFire.range > 0 &&
|
this.autoFire.range > 0 &&
|
||||||
time >= this.nextFireAt
|
time >= this.nextFireAt
|
||||||
) {
|
) {
|
||||||
const target = this.nearestEnemy(player.x, player.y);
|
const target = pickTarget(
|
||||||
if (target && Math.hypot(target.x - player.x, target.y - player.y) <= this.autoFire.range) {
|
this.enemies,
|
||||||
this.firePlayerAt(player, target, time);
|
player.x,
|
||||||
}
|
player.y,
|
||||||
|
this.autoFire.range,
|
||||||
|
(e) => this.craftVisible(e),
|
||||||
|
);
|
||||||
|
if (target) this.firePlayerAt(player, target, time);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Tracers: age + cull (no walls here — the arena is the tether range).
|
// 3) Tracers: age + cull (no walls here — the arena is the tether range).
|
||||||
|
|
@ -432,13 +472,20 @@ export class CombatSystem {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The CURRENT TARGET (the readability layer's lock, js/combat/CombatHud.js):
|
* The CURRENT TARGET (the readability layer's lock, js/combat/CombatHud.js):
|
||||||
* the hostile the auto-fire is on — the nearest one WITHIN auto-fire
|
* the hostile the auto-fire is on — the nearest VISIBLE one WITHIN
|
||||||
* range (the same pickTarget rule, ranged by game.autoFire.range — so
|
* auto-fire range (the same pickTarget rule, ranged by game.autoFire.range
|
||||||
* the brackets sit on exactly the ship the cannon is tracking).
|
* and narrowed by the on-screen rule — so the brackets sit on exactly the
|
||||||
|
* ship the cannon can engage, never an off-screen one).
|
||||||
*/
|
*/
|
||||||
currentTarget() {
|
currentTarget() {
|
||||||
if (!this.ship) return null;
|
if (!this.ship) return null;
|
||||||
return pickTarget(this.enemies, this.ship.x, this.ship.y, this.autoFire.range);
|
return pickTarget(
|
||||||
|
this.enemies,
|
||||||
|
this.ship.x,
|
||||||
|
this.ship.y,
|
||||||
|
this.autoFire.range,
|
||||||
|
(e) => this.craftVisible(e),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -557,6 +604,7 @@ export class CombatSystem {
|
||||||
if (b.dead || b.owner !== 'player') continue;
|
if (b.dead || b.owner !== 'player') continue;
|
||||||
for (const e of this.enemies) {
|
for (const e of this.enemies) {
|
||||||
if (e.dead) continue;
|
if (e.dead) continue;
|
||||||
|
if (!this.craftVisible(e)) continue; // off-screen: can't be hit
|
||||||
if (this.hitsSwept(b, e, e.hitRadius, delta)) {
|
if (this.hitsSwept(b, e, e.hitRadius, delta)) {
|
||||||
const preShields = e.shields;
|
const preShields = e.shields;
|
||||||
const killed = e.damage(b.damage);
|
const killed = e.damage(b.damage);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue