Compare commits
No commits in common. "7b48e030efabd612e6a26c1bcb02daa755adeb55" and "cff8fa335e53ce067634b1f3cba544ee9b332d63" have entirely different histories.
7b48e030ef
...
cff8fa335e
Binary file not shown.
|
Before Width: | Height: | Size: 2.4 MiB |
Binary file not shown.
|
|
@ -2,24 +2,19 @@
|
||||||
* Mining flow (headless browser — NOT a Node test).
|
* Mining flow (headless browser — NOT a Node test).
|
||||||
*
|
*
|
||||||
* Drives the REAL game's input pipeline (mousedown/keydown on the canvas)
|
* Drives the REAL game's input pipeline (mousedown/keydown on the canvas)
|
||||||
* and walks the whole arm sequence against a seeded galaxy — including the
|
* and walks the whole arm sequence against a seeded galaxy:
|
||||||
* SHIP'S STATE (js/entities/Ship.js): 'normal' by default, 'mining' while
|
|
||||||
* the arm's sequence runs, and ANY movement (a world click or the compass
|
|
||||||
* autopilot) drops it back to 'normal' and ends the sequence:
|
|
||||||
*
|
*
|
||||||
* click a rock → the menu opens at the click (MINE ASTEROIDS)
|
* click a rock → the menu opens at the click (MINE ASTEROIDS)
|
||||||
* a click OUTSIDE it → closes, and does not fly the ship
|
* a click OUTSIDE it → closes, and does not fly the ship
|
||||||
* click MINE ASTEROIDS → "extending": the ship is in 'mining' state,
|
* click MINE ASTEROIDS → "extending" (ship locked, console toast
|
||||||
* holds station, console says "Extending..."
|
* "Extending Mining Arm...")
|
||||||
* a world click meanwhile → MOVES the ship: the extension ABORTS, the
|
* a world click meanwhile → is held (no fly, no abort)
|
||||||
* ship goes back to 'normal' and flies
|
* the ~1.5 s reach → "mining": the beam is live, ore motes ride it
|
||||||
* mine again, the ~1.5 s → "mining": beam live, ore motes ride it,
|
* click the rock again → the menu now says STOP MINING
|
||||||
* reach the ship still 'mining'
|
* click STOP MINING → the beam retracts, then the arm is idle
|
||||||
* the COMPASS AUTOPILOT → MOVES the ship: 'normal' again, the beam
|
* ESC while mining → breaks the beam
|
||||||
* retracts as the ship goes
|
* ESC with the menu open → closes it
|
||||||
* mine again; ESC → breaks the beam, the ship is 'normal'
|
* click CANCEL → closes, no mining
|
||||||
* ESC with the menu open → closes it, no mining
|
|
||||||
* click CANCEL → closes, no mining, ship 'normal'
|
|
||||||
*
|
*
|
||||||
* HEADLESS PUMP: this box's headless Firefox freezes rAF and timers once
|
* HEADLESS PUMP: this box's headless Firefox freezes rAF and timers once
|
||||||
* the first paint is done, so the page can't run itself. The runner
|
* the first paint is done, so the page can't run itself. The runner
|
||||||
|
|
@ -157,36 +152,9 @@ const rockScreen = () => {
|
||||||
const s = game.scene.getScene('GameScene');
|
const s = game.scene.getScene('GameScene');
|
||||||
return w2s(rock.wx, rock.wy);
|
return w2s(rock.wx, rock.wy);
|
||||||
};
|
};
|
||||||
// A screen point guaranteed to be OFF every rock of the cluster (the
|
|
||||||
// members are scattered — a fixed point can land on one) AND outside the
|
|
||||||
// mining panel's footprint (a click right after a button press that
|
|
||||||
// lands on the panel area is consumed as the panel's own click).
|
|
||||||
const openSpaceScreen = () => {
|
const openSpaceScreen = () => {
|
||||||
const s = game.scene.getScene('GameScene');
|
const s = game.scene.getScene('GameScene');
|
||||||
const cam = s.cameras.main;
|
return { x: s.scale.width / 2, y: s.scale.height / 2 };
|
||||||
const W = s.scale.width, H = s.scale.height;
|
|
||||||
const p = s.miningPopup;
|
|
||||||
const inPanel = (wx, wy) => {
|
|
||||||
const r = p && p.rect;
|
|
||||||
if (!r) return false;
|
|
||||||
return wx >= r.x - 20 && wx <= r.x + r.w + 20 && wy >= r.y - 20 && wy <= r.y + r.h + 20;
|
|
||||||
};
|
|
||||||
const cands = [
|
|
||||||
[W / 2, H / 2], [120, H / 2], [W - 120, H / 2],
|
|
||||||
[W / 2, 120], [W / 2, H - 120], [120, 120], [W - 120, H - 120],
|
|
||||||
];
|
|
||||||
for (const [cx, cy] of cands) {
|
|
||||||
const wx = cam.scrollX + cx, wy = cam.scrollY + cy;
|
|
||||||
let clear = true;
|
|
||||||
for (const m of (cluster ? cluster.members : [])) {
|
|
||||||
if (Math.hypot(m.wx - wx, m.wy - wy) < m.radius + 40) { clear = false; break; }
|
|
||||||
}
|
|
||||||
// Far enough from the ship that the flight stays observable (a click
|
|
||||||
// next to the ship arrives instantly and clears the target).
|
|
||||||
const fromShip = Math.hypot(s.ship.x - wx, s.ship.y - wy) > 400;
|
|
||||||
if (clear && fromShip && !inPanel(wx, wy)) return { x: cx, y: cy };
|
|
||||||
}
|
|
||||||
return { x: 120, y: 120 }; // last resort (practically unreachable)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
|
|
@ -208,33 +176,10 @@ const finish = (pass) => {
|
||||||
const errors = (window.__CAPTURED_ERRORS__ || []).concat(pumpErrors);
|
const errors = (window.__CAPTURED_ERRORS__ || []).concat(pumpErrors);
|
||||||
if (errors.length === 0) all.push({ label: 'no console errors were captured', pass: true });
|
if (errors.length === 0) all.push({ label: 'no console errors were captured', pass: true });
|
||||||
const ok = all.every((r) => r.pass);
|
const ok = all.every((r) => r.pass);
|
||||||
window.__MINING__ = { pass: ok, results: all, errors, dbg: window.__DBG__STATE__ || null };
|
window.__MINING__ = { pass: ok, results: all, errors };
|
||||||
console.log(ok ? 'MINING PASS' : 'MINING FAIL');
|
console.log(ok ? 'MINING PASS' : 'MINING FAIL');
|
||||||
};
|
};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
// Diagnostics: the state machine can stash snapshots here; finish()
|
|
||||||
// ships them back in window.__MINING__.dbg.
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
const dbg = (window.__DBG__STATE__ = {});
|
|
||||||
const snapshot = (tag) => {
|
|
||||||
try {
|
|
||||||
const s = scene();
|
|
||||||
const arr = (dbg[tag] = dbg[tag] || []);
|
|
||||||
arr.push({
|
|
||||||
t: Math.round(s.time.now),
|
|
||||||
mining: s.mining.state,
|
|
||||||
ship: s.ship.state,
|
|
||||||
target: s.ship.target ? [Math.round(s.ship.target.x), Math.round(s.ship.target.y)] : null,
|
|
||||||
xy: [Math.round(s.ship.x), Math.round(s.ship.y)],
|
|
||||||
scroll: [Math.round(s.cameras.main.scrollX), Math.round(s.cameras.main.scrollY)],
|
|
||||||
popup: s.miningPopup ? s.miningPopup.state : null,
|
|
||||||
stamp: s.miningPopup ? s.miningPopup.closedByButtonAt : null,
|
|
||||||
});
|
|
||||||
if (arr.length > 30) arr.shift();
|
|
||||||
} catch { /* booting */ }
|
|
||||||
};
|
|
||||||
|
|
||||||
const scene = () => game.scene.getScene('GameScene');
|
const scene = () => game.scene.getScene('GameScene');
|
||||||
|
|
||||||
const fail = (label) => {
|
const fail = (label) => {
|
||||||
|
|
@ -242,41 +187,6 @@ const fail = (label) => {
|
||||||
finish(false);
|
finish(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Park the ship by the rock and frame it (the test's teleport). */
|
|
||||||
const parkNearRock = () => {
|
|
||||||
const s = scene();
|
|
||||||
s.ship.stop();
|
|
||||||
const sx = rock.wx - 190;
|
|
||||||
s.ship.setPosition(sx, rock.wy);
|
|
||||||
s.cameras.main.setScroll(
|
|
||||||
(sx + rock.wx) / 2 - s.scale.width / 2,
|
|
||||||
rock.wy - s.scale.height / 2,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// The camera eases onto the ship (GameScene.updateCamera) — after a park
|
|
||||||
// or a flight it keeps drifting for a while. Pump until it settles so a
|
|
||||||
// rock's screen position is stable when we compute the next click.
|
|
||||||
const settleCamera = (maxBatches = 60) => {
|
|
||||||
let last = null;
|
|
||||||
let still = 0;
|
|
||||||
for (let i = 0; i < maxBatches; i++) {
|
|
||||||
pump(6, 80);
|
|
||||||
try {
|
|
||||||
const cam = scene().cameras.main;
|
|
||||||
const cur = [cam.scrollX, cam.scrollY];
|
|
||||||
if (last) {
|
|
||||||
if (Math.abs(cur[0] - last[0]) < 0.5 && Math.abs(cur[1] - last[1]) < 0.5) {
|
|
||||||
still++;
|
|
||||||
if (still >= 3) return true;
|
|
||||||
} else still = 0;
|
|
||||||
}
|
|
||||||
last = cur;
|
|
||||||
} catch { /* booting */ }
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const stepMachine = () => {
|
const stepMachine = () => {
|
||||||
let s = null;
|
let s = null;
|
||||||
try { s = scene(); } catch { /* not booted yet */ }
|
try { s = scene(); } catch { /* not booted yet */ }
|
||||||
|
|
@ -298,8 +208,13 @@ const stepMachine = () => {
|
||||||
}
|
}
|
||||||
cluster = s.asteroidClusters[0];
|
cluster = s.asteroidClusters[0];
|
||||||
rock = cluster.members[0];
|
rock = cluster.members[0];
|
||||||
parkNearRock();
|
s.ship.stop();
|
||||||
settleCamera();
|
const sx = rock.wx - 190;
|
||||||
|
s.ship.setPosition(sx, rock.wy);
|
||||||
|
s.cameras.main.setScroll(
|
||||||
|
(sx + rock.wx) / 2 - s.scale.width / 2,
|
||||||
|
rock.wy - s.scale.height / 2,
|
||||||
|
);
|
||||||
settle(200);
|
settle(200);
|
||||||
press(rockScreen().x, rockScreen().y);
|
press(rockScreen().x, rockScreen().y);
|
||||||
stage = 1;
|
stage = 1;
|
||||||
|
|
@ -320,7 +235,6 @@ const stepMachine = () => {
|
||||||
s.miningPopup.headerText.text.toUpperCase().includes(String(cluster.discoveryName).toUpperCase()));
|
s.miningPopup.headerText.text.toUpperCase().includes(String(cluster.discoveryName).toUpperCase()));
|
||||||
check('the rock click did NOT fly the ship', s.ship.target === null);
|
check('the rock click did NOT fly the ship', s.ship.target === null);
|
||||||
check('the rock click did NOT start mining', s.mining.state === 'idle');
|
check('the rock click did NOT start mining', s.mining.state === 'idle');
|
||||||
check('the ship is in its NORMAL state', s.ship.state === 'normal');
|
|
||||||
press(openSpaceScreen().x, openSpaceScreen().y); // open space — outside the panel
|
press(openSpaceScreen().x, openSpaceScreen().y); // open space — outside the panel
|
||||||
stage = 2;
|
stage = 2;
|
||||||
return;
|
return;
|
||||||
|
|
@ -365,8 +279,8 @@ const stepMachine = () => {
|
||||||
if (!r) fail('MINE ASTEROIDS starts the extension');
|
if (!r) fail('MINE ASTEROIDS starts the extension');
|
||||||
check('MINE ASTEROIDS starts the extension', true);
|
check('MINE ASTEROIDS starts the extension', true);
|
||||||
check('the menu closed on the button press', s.miningPopup.isOpen === false);
|
check('the menu closed on the button press', s.miningPopup.isOpen === false);
|
||||||
check('the ship is in the MINING state while the arm extends', s.ship.state === 'mining');
|
check('the ship is locked while the arm extends',
|
||||||
check('the ship holds station while the arm extends', s.ship.target === null);
|
s.ship.target === null && s.mining.isLocked === true);
|
||||||
if (!sawExtendingToast) {
|
if (!sawExtendingToast) {
|
||||||
// One more chance while the arm is still out (the toast holds the
|
// One more chance while the arm is still out (the toast holds the
|
||||||
// whole extension window).
|
// whole extension window).
|
||||||
|
|
@ -379,95 +293,26 @@ const stepMachine = () => {
|
||||||
}
|
}
|
||||||
check('the console says Extending Mining Arm...', sawExtendingToast);
|
check('the console says Extending Mining Arm...', sawExtendingToast);
|
||||||
|
|
||||||
// A world click mid-extension MOVES the ship — which ends the
|
// A world click mid-extension holds (no fly, no abort).
|
||||||
// mining state (the new contract: movement beats the arm's reach).
|
if (s.mining.state === 'extending') press(openSpaceScreen().x, openSpaceScreen().y);
|
||||||
if (s.mining.state === 'extending') {
|
|
||||||
const p5 = openSpaceScreen();
|
|
||||||
dbg.stage4press = p5;
|
|
||||||
press(p5.x, p5.y);
|
|
||||||
}
|
|
||||||
stage = 5;
|
stage = 5;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 5) the movement broke the arm; set up the real attempt ----------
|
// ---- 5) the reach completes into mining --------------------------------
|
||||||
case 5: {
|
case 5: {
|
||||||
// If the "open space" click landed on another member of the cluster
|
|
||||||
// (the members are scattered), it opened the menu instead of flying
|
|
||||||
// — close it and click true open space.
|
|
||||||
if (s.miningPopup.isOpen) {
|
|
||||||
esc();
|
|
||||||
settle(150);
|
|
||||||
const p = openSpaceScreen();
|
|
||||||
dbg.stage5repress = p;
|
|
||||||
press(p.x, p.y);
|
|
||||||
}
|
|
||||||
const r = wait(() => s.mining.state === 'idle' && s.mining.beam === null,
|
|
||||||
() => ['extending', 'mining', 'retracting', 'idle'].includes(s.mining.state),
|
|
||||||
() => snapshot('stage5'), 1200, 90000);
|
|
||||||
if (r === null) return;
|
|
||||||
if (!r) fail('a world click breaks the arm (abort or retract)');
|
|
||||||
check('a world click breaks the arm (abort or retract)', true);
|
|
||||||
check('the beam is gone (aborted, or retracted and reaped)', s.mining.beam === null);
|
|
||||||
check('the ship is back in the NORMAL state', s.ship.state === 'normal');
|
|
||||||
check('the breaking click flew the ship', s.ship.target !== null);
|
|
||||||
|
|
||||||
// Set up the next attempt: park by the rock, let the camera settle,
|
|
||||||
// menu, MINE.
|
|
||||||
parkNearRock();
|
|
||||||
settleCamera();
|
|
||||||
dbg.stage6press = rockScreen();
|
|
||||||
press(rockScreen().x, rockScreen().y);
|
|
||||||
stage = 6;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- 6) menu again; start mining for real ------------------------------
|
|
||||||
case 6: {
|
|
||||||
const r = wait(() => s.miningPopup.isOpen === true, () => true,
|
|
||||||
() => snapshot('stage6'), 1500, 20000);
|
|
||||||
if (r === null) return;
|
|
||||||
if (!r) {
|
|
||||||
const pp = dbg.stage6press || rockScreen();
|
|
||||||
dbg.stage6 = {
|
|
||||||
press: pp,
|
|
||||||
rockHit: !!s.rockAt(rock.wx, rock.wy),
|
|
||||||
rockScreenNow: rockScreen(),
|
|
||||||
savePanelOpen: !!(s.savePanel && s.savePanel.isOpen),
|
|
||||||
subbarOpen: !!(s.menuSubBar && s.menuSubBar.isOpen),
|
|
||||||
actionbarHit: !!(s.actionBar && s.actionBar.contains(pp.x, pp.y)),
|
|
||||||
compassHit: !!s.compass.contains(pp.x, pp.y),
|
|
||||||
titleHit: !!s.hudTitleContains(pp.x, pp.y),
|
|
||||||
popupState: s.miningPopup ? s.miningPopup.state : null,
|
|
||||||
miningState: s.mining.state,
|
|
||||||
shipTarget: s.ship.target,
|
|
||||||
};
|
|
||||||
fail('the menu re-opens (mine-for-real test)');
|
|
||||||
}
|
|
||||||
const b = btnScreen(s.miningPopup.primaryBtn);
|
|
||||||
press(b.x, b.y);
|
|
||||||
stage = 7;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- 7) the reach completes into mining --------------------------------
|
|
||||||
case 7: {
|
|
||||||
const r = wait(() => s.mining.state === 'mining',
|
const r = wait(() => s.mining.state === 'mining',
|
||||||
() => ['extending', 'mining'].includes(s.mining.state),
|
() => s.mining.state === 'extending' || s.mining.state === 'mining',
|
||||||
null, 1200, 90000);
|
null, 1200, 90000);
|
||||||
if (r === null) return;
|
if (r === null) return;
|
||||||
if (!r) fail('the extension completes into mining');
|
if (!r) fail('the extension completes into mining');
|
||||||
check('the extension completes into mining', true);
|
check('the extension completes into mining', true);
|
||||||
check('the ship is still in the MINING state', s.ship.state === 'mining');
|
check('clicks held while the arm extends (no fly)', s.ship.target === null);
|
||||||
check('the beam is live', !!s.mining.beam && (s.mining.beam.state === 'in' || s.mining.beam.state === 'steady'));
|
check('the beam is live', !!s.mining.beam && (s.mining.beam.state === 'in' || s.mining.beam.state === 'steady'));
|
||||||
check('the beam is locked to the clicked rock',
|
check('the beam is locked to the clicked rock',
|
||||||
!!s.mining.beam && s.mining.beam.member === rock && s.mining.member === rock);
|
!!s.mining.beam && s.mining.beam.member === rock && s.mining.member === rock);
|
||||||
stage = 8;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- 8) beam steady + ore; then the AUTOPILOT moves the ship -----------
|
// Let the beam settle to steady + the ore stream build.
|
||||||
case 8: {
|
|
||||||
const r2 = wait(
|
const r2 = wait(
|
||||||
() => !!s.mining.beam && s.mining.beam.state === 'steady' && s.mining.beam.particles.some((p) => p.active),
|
() => !!s.mining.beam && s.mining.beam.state === 'steady' && s.mining.beam.particles.some((p) => p.active),
|
||||||
() => !!s.mining.beam && s.mining.state === 'mining',
|
() => !!s.mining.beam && s.mining.state === 'mining',
|
||||||
|
|
@ -476,133 +321,139 @@ const stepMachine = () => {
|
||||||
if (!r2) fail('the beam settles and ore motes ride it');
|
if (!r2) fail('the beam settles and ore motes ride it');
|
||||||
check('the beam is live and steady', true);
|
check('the beam is live and steady', true);
|
||||||
check('ore motes ride the beam toward the ship', true);
|
check('ore motes ride the beam toward the ship', true);
|
||||||
check('the ship is in the MINING state while the beam is live', s.ship.state === 'mining');
|
|
||||||
|
|
||||||
// The compass autopilot seam — the player sends the ship elsewhere
|
press(rockScreen().x, rockScreen().y); // the beam target, again
|
||||||
// (a different discovered object — the mining cluster's rim is right
|
stage = 6;
|
||||||
// next to the ship and the flight would "arrive" before it's
|
return;
|
||||||
// observable): movement must drop the ship back to 'normal' and end
|
}
|
||||||
// the mining.
|
|
||||||
const objs = s.discoverableObjects();
|
// ---- 6) the target menu now offers STOP MINING ------------------------
|
||||||
const other = objs.find((o) => o.id !== cluster.discoveryId) || objs[0];
|
case 6: {
|
||||||
dbg.autopilotTarget = {
|
const r = wait(() => s.miningPopup.isOpen === true, () => true, null, 1500, 20000);
|
||||||
id: other.id,
|
if (r === null) return;
|
||||||
distFromShip: Math.round(Math.hypot(other.x - s.ship.x, other.y - s.ship.y)),
|
if (!r) fail('the menu opens on the mining target');
|
||||||
};
|
check('the menu opens on the mining target', true);
|
||||||
s.autopilotTo(other.id);
|
check('the primary button now says STOP MINING',
|
||||||
|
s.miningPopup.primaryBtn.labelText.text === 'STOP MINING');
|
||||||
|
const b = btnScreen(s.miningPopup.primaryBtn);
|
||||||
|
press(b.x, b.y);
|
||||||
|
stage = 7;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 7) stop → retract --------------------------------------------------
|
||||||
|
case 7: {
|
||||||
|
const r = wait(() => s.mining.state === 'retracting' || s.mining.state === 'idle',
|
||||||
|
() => ['retracting', 'mining', 'idle'].includes(s.mining.state), null, 1200, 60000);
|
||||||
|
if (r === null) return;
|
||||||
|
if (!r) fail('STOP MINING retracts the beam');
|
||||||
|
check('STOP MINING retracts the beam', true);
|
||||||
|
check('the ship is free again while it retracts', s.mining.isLocked === false);
|
||||||
|
stage = 8;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 8) the beam is reaped and the arm is idle ---------------------------
|
||||||
|
case 8: {
|
||||||
|
const r = wait(() => s.mining.state === 'idle' && s.mining.beam === null,
|
||||||
|
() => ['retracting', 'idle'].includes(s.mining.state), null, 1200, 60000);
|
||||||
|
if (r === null) return;
|
||||||
|
if (!r) fail('the beam is reaped and the arm is idle');
|
||||||
|
check('the beam is reaped and the arm is idle', true);
|
||||||
|
|
||||||
|
press(rockScreen().x, rockScreen().y); // mine again (ESC test)
|
||||||
stage = 9;
|
stage = 9;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 9) the autopilot break: normal state, beam reaped ------------------
|
// ---- 9) menu again; start a second mining -------------------------------
|
||||||
case 9: {
|
case 9: {
|
||||||
const r = wait(() => s.mining.state === 'idle' && s.mining.beam === null,
|
|
||||||
() => ['mining', 'retracting', 'idle'].includes(s.mining.state), null, 1200, 90000);
|
|
||||||
if (r === null) return;
|
|
||||||
if (!r) fail('the autopilot ends the mining and reaps the beam');
|
|
||||||
check('the autopilot ends the mining and reaps the beam', true);
|
|
||||||
check('the ship is back in the NORMAL state', s.ship.state === 'normal');
|
|
||||||
check('the autopilot flew the ship', s.ship.target !== null);
|
|
||||||
|
|
||||||
parkNearRock();
|
|
||||||
settleCamera();
|
|
||||||
press(rockScreen().x, rockScreen().y); // mine again (ESC test)
|
|
||||||
stage = 10;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- 10) menu again; start a third mining ---------------------------------
|
|
||||||
case 10: {
|
|
||||||
const r = wait(() => s.miningPopup.isOpen === true, () => true, null, 1500, 20000);
|
const r = wait(() => s.miningPopup.isOpen === true, () => true, null, 1500, 20000);
|
||||||
if (r === null) return;
|
if (r === null) return;
|
||||||
if (!r) fail('the menu opens again (ESC test)');
|
if (!r) fail('the menu opens again (ESC test)');
|
||||||
const b = btnScreen(s.miningPopup.primaryBtn);
|
const b = btnScreen(s.miningPopup.primaryBtn);
|
||||||
press(b.x, b.y);
|
press(b.x, b.y);
|
||||||
stage = 11;
|
stage = 10;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 11) mining is live again; ESC breaks it --------------------------------
|
// ---- 10) mining is live again; ESC breaks it ------------------------------
|
||||||
case 11: {
|
case 10: {
|
||||||
const r = wait(() => s.mining.state === 'mining',
|
const r = wait(() => s.mining.state === 'mining',
|
||||||
() => ['extending', 'mining'].includes(s.mining.state), null, 1200, 90000);
|
() => ['extending', 'mining'].includes(s.mining.state), null, 1200, 90000);
|
||||||
if (r === null) return;
|
if (r === null) return;
|
||||||
if (!r) fail('mining is live again (ESC test)');
|
if (!r) fail('mining is live again (ESC test)');
|
||||||
check('mining is live again (ESC test)', true);
|
check('mining is live again (ESC test)', true);
|
||||||
check('the ship is in the MINING state', s.ship.state === 'mining');
|
|
||||||
esc();
|
esc();
|
||||||
stage = 12;
|
stage = 11;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 12) the ESC stop landed ------------------------------------------------
|
// ---- 11) the ESC stop landed ----------------------------------------------
|
||||||
case 12: {
|
case 11: {
|
||||||
const r = wait(() => s.mining.state === 'retracting' || s.mining.state === 'idle',
|
const r = wait(() => s.mining.state === 'retracting' || s.mining.state === 'idle',
|
||||||
() => ['retracting', 'mining', 'idle'].includes(s.mining.state), null, 1200, 60000);
|
() => ['retracting', 'mining', 'idle'].includes(s.mining.state), null, 1200, 60000);
|
||||||
if (r === null) return;
|
if (r === null) return;
|
||||||
if (!r) fail('ESC breaks the beam');
|
if (!r) fail('ESC breaks the beam');
|
||||||
check('ESC breaks the beam', true);
|
check('ESC breaks the beam', true);
|
||||||
stage = 13;
|
stage = 12;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 13) arm idle after the ESC stop; the ship is NORMAL again ------------
|
// ---- 12) arm idle after the ESC stop ---------------------------------------
|
||||||
case 13: {
|
case 12: {
|
||||||
const r = wait(() => s.mining.state === 'idle' && s.mining.beam === null,
|
const r = wait(() => s.mining.state === 'idle' && s.mining.beam === null,
|
||||||
() => ['retracting', 'idle'].includes(s.mining.state), null, 1200, 60000);
|
() => ['retracting', 'idle'].includes(s.mining.state), null, 1200, 60000);
|
||||||
if (r === null) return;
|
if (r === null) return;
|
||||||
if (!r) fail('the arm is idle after the ESC stop');
|
if (!r) fail('the arm is idle after the ESC stop');
|
||||||
check('the arm is idle after the ESC stop', true);
|
check('the arm is idle after the ESC stop', true);
|
||||||
check('the ship is back in the NORMAL state', s.ship.state === 'normal');
|
|
||||||
|
|
||||||
press(rockScreen().x, rockScreen().y); // menu again (ESC-closes test)
|
press(rockScreen().x, rockScreen().y); // menu again (ESC-closes test)
|
||||||
stage = 14;
|
stage = 13;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 14) ESC closes an open menu --------------------------------------------
|
// ---- 13) ESC closes an open menu ---------------------------------------------
|
||||||
case 14: {
|
case 13: {
|
||||||
const r = wait(() => s.miningPopup.isOpen === true, () => true, null, 1500, 20000);
|
const r = wait(() => s.miningPopup.isOpen === true, () => true, null, 1500, 20000);
|
||||||
if (r === null) return;
|
if (r === null) return;
|
||||||
if (!r) fail('the menu opens again (ESC-closes test)');
|
if (!r) fail('the menu opens again (ESC-closes test)');
|
||||||
esc();
|
esc();
|
||||||
stage = 15;
|
stage = 14;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 15) the menu closed; now CANCEL ----------------------------------------
|
// ---- 14) the menu closed; now CANCEL ------------------------------------------
|
||||||
case 15: {
|
case 14: {
|
||||||
const r = wait(() => s.miningPopup.isOpen === false, () => true, null, 1500, 20000);
|
const r = wait(() => s.miningPopup.isOpen === false, () => true, null, 1500, 20000);
|
||||||
if (r === null) return;
|
if (r === null) return;
|
||||||
if (!r) fail('ESC closes the open menu');
|
if (!r) fail('ESC closes the open menu');
|
||||||
check('ESC closes the open menu', true);
|
check('ESC closes the open menu', true);
|
||||||
check('...and did not start mining', s.mining.state === 'idle');
|
check('...and did not start mining', s.mining.state === 'idle');
|
||||||
check('the ship is in the NORMAL state', s.ship.state === 'normal');
|
|
||||||
press(rockScreen().x, rockScreen().y);
|
press(rockScreen().x, rockScreen().y);
|
||||||
stage = 16;
|
stage = 15;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 16) CANCEL closes without mining ---------------------------------------
|
// ---- 15) CANCEL closes without mining --------------------------------------------
|
||||||
case 16: {
|
case 15: {
|
||||||
const r = wait(() => s.miningPopup.isOpen === true, () => true, null, 1500, 20000);
|
const r = wait(() => s.miningPopup.isOpen === true, () => true, null, 1500, 20000);
|
||||||
if (r === null) return;
|
if (r === null) return;
|
||||||
if (!r) fail('the menu opens again (CANCEL test)');
|
if (!r) fail('the menu opens again (CANCEL test)');
|
||||||
const c = btnScreen(s.miningPopup.cancelBtn);
|
const c = btnScreen(s.miningPopup.cancelBtn);
|
||||||
press(c.x, c.y);
|
press(c.x, c.y);
|
||||||
stage = 17;
|
stage = 16;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 17) done -----------------------------------------------------------------
|
// ---- 16) done ----------------------------------------------------------------------
|
||||||
case 17: {
|
case 16: {
|
||||||
const r = wait(() => s.miningPopup.isOpen === false, () => true, null, 1500, 20000);
|
const r = wait(() => s.miningPopup.isOpen === false, () => true, null, 1500, 20000);
|
||||||
if (r === null) return;
|
if (r === null) return;
|
||||||
if (!r) fail('CANCEL closes the menu');
|
if (!r) fail('CANCEL closes the menu');
|
||||||
check('CANCEL closes the menu', true);
|
check('CANCEL closes the menu', true);
|
||||||
check('CANCEL does not start mining', s.mining.state === 'idle');
|
check('CANCEL does not start mining', s.mining.state === 'idle');
|
||||||
check('CANCEL does not fly the ship', s.ship.target === null);
|
check('CANCEL does not fly the ship', s.ship.target === null);
|
||||||
check('the ship is in the NORMAL state', s.ship.state === 'normal');
|
|
||||||
finish(results.every((x) => x.pass));
|
finish(results.every((x) => x.pass));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -99,49 +99,11 @@ export class Ship extends Phaser.Physics.Arcade.Sprite {
|
||||||
this.radius = worldSize / 2;
|
this.radius = worldSize / 2;
|
||||||
|
|
||||||
this.target = null;
|
this.target = null;
|
||||||
|
|
||||||
// SHIP STATE — what the ship is doing right now. 'normal' is the
|
|
||||||
// default: free to fly wherever the player sends it. 'mining' = the
|
|
||||||
// arm's sequence owns the ship (js/mining/Mining.js drives it; the
|
|
||||||
// beam + ore stream only live while the state holds). More states
|
|
||||||
// will land here (docking, boarding, …). Any change OUT of a state —
|
|
||||||
// another state, or the player MOVING the ship (setTarget, autopilot)
|
|
||||||
// — is signalled via onStateChange, and the scene's handler tears
|
|
||||||
// down whatever that state was doing (ends the mining sequence).
|
|
||||||
this.state = 'normal'; // 'normal' | 'mining' (later: more)
|
|
||||||
this.onStateChange = null; // (next, prev, reason) => void — the scene installs
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Change the ship's state ('normal' | 'mining' | …). A no-op when the
|
|
||||||
* state is unchanged. On a real change, onStateChange fires — the scene
|
|
||||||
* ends the mining sequence when the ship leaves 'mining', whichever
|
|
||||||
* state (or movement) took it out.
|
|
||||||
*
|
|
||||||
* @returns {boolean} true when the state actually changed
|
|
||||||
*/
|
|
||||||
setState(next, reason) {
|
|
||||||
if (next === this.state) return false;
|
|
||||||
const prev = this.state;
|
|
||||||
this.state = next;
|
|
||||||
if (typeof this.onStateChange === 'function') {
|
|
||||||
try {
|
|
||||||
this.onStateChange(next, prev, reason);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('[ship] onStateChange handler failed', err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Set the destination to fly to (world coordinates). */
|
/** Set the destination to fly to (world coordinates). */
|
||||||
setTarget(x, y) {
|
setTarget(x, y) {
|
||||||
this.target = { x, y };
|
this.target = { x, y };
|
||||||
// Moving the ship ends whatever non-normal state it was in (mining,
|
|
||||||
// and later states): the player's movement always wins, so any state
|
|
||||||
// hands the ship back to 'normal' — the scene's onStateChange handler
|
|
||||||
// finishes the teardown (retracts the beam, releases the arm).
|
|
||||||
this.setState('normal', 'move');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Stop steering and brake immediately. */
|
/** Stop steering and brake immediately. */
|
||||||
|
|
|
||||||
|
|
@ -2,27 +2,20 @@ import { config } from '../config/Config.js';
|
||||||
import { MiningBeam } from './MiningBeam.js';
|
import { MiningBeam } from './MiningBeam.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ship's mining state — the machine behind the beam. The SHIP'S
|
* The ship's mining state — the machine behind the beam:
|
||||||
* state gates it (js/entities/Ship.js): the sequence runs only while
|
|
||||||
* the ship is in its 'mining' state — the scene puts the ship there
|
|
||||||
* when the arm engages and takes it back to 'normal' when the sequence
|
|
||||||
* ends. Moving the ship (a world click, the autopilot) or any other
|
|
||||||
* state change drops it out of 'mining', which ends the sequence:
|
|
||||||
* the scene's onStateChange handler does the teardown, and update()
|
|
||||||
* below enforces the gate.
|
|
||||||
*
|
*
|
||||||
* idle ──begin()──► extending ──(armExtendMs)──► mining ──stop()──► retracting
|
* idle ──begin()──► extending ──(armExtendMs)──► mining ──stop()──► retracting
|
||||||
* ▲ │ ▲ │ │
|
* ▲ │ ▲ │ │
|
||||||
* │ └──┘ (stop() cuts the extension) └────beam out────┘
|
* │ └──┘ (stop() cuts the extension) └────beam out────┘
|
||||||
* └──────────────────────────────────────────────────────────────────────┘
|
* └──────────────────────────────────────────────────────────────────────┘
|
||||||
*
|
*
|
||||||
* extending the arm is reaching for the rock — the ship holds
|
* extending the arm is reaching for the rock — the ship is LOCKED
|
||||||
* station (GameScene stops it) and sits in 'mining';
|
* (GameScene stops it) and the world holds;
|
||||||
* mining the beam is live (MiningBeam) — the ship stays at its
|
* mining the beam is live (MiningBeam) — the ship stays LOCKED at
|
||||||
* rim, still 'mining'; retargeting (begin on another rock)
|
* its rim; retargeting (begin on another rock) cuts the
|
||||||
* cuts the current beam and re-extends;
|
* current beam and re-extends;
|
||||||
* retracting the beam is pulling back rock → ship; the ship is already
|
* retracting the beam is pulling back rock → ship; the ship is already
|
||||||
* BACK TO 'normal' (FREE) — the player can fly as it dies.
|
* FREE (isLocked is false) — the player can fly as it dies.
|
||||||
*
|
*
|
||||||
* This class owns only the sequence + the beam's lifecycle. Scene
|
* This class owns only the sequence + the beam's lifecycle. Scene
|
||||||
* concerns (ship stop, toasts, sfx) ride the onPhase seam — the same
|
* concerns (ship stop, toasts, sfx) ride the onPhase seam — the same
|
||||||
|
|
@ -45,8 +38,8 @@ export class Mining {
|
||||||
this.armExtendMs = config.get('asteroids.mining.armExtendMs', 1500);
|
this.armExtendMs = config.get('asteroids.mining.armExtendMs', 1500);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The arm's sequence is live: mid-extension OR beam live. */
|
/** The ship may not fly: mid-extension OR beam live. */
|
||||||
get isActive() {
|
get isLocked() {
|
||||||
return this.state === 'extending' || this.state === 'mining';
|
return this.state === 'extending' || this.state === 'mining';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,19 +84,6 @@ export class Mining {
|
||||||
* mining hand-off and reaps a finished retract.
|
* mining hand-off and reaps a finished retract.
|
||||||
*/
|
*/
|
||||||
update(time, delta) {
|
update(time, delta) {
|
||||||
// The ship's state is the gate: the sequence (and its visuals) run
|
|
||||||
// only while the ship is in its 'mining' state. It left that state —
|
|
||||||
// the player moved the ship, or a state change — so end the sequence
|
|
||||||
// right here (belt & braces over the scene's onStateChange handler).
|
|
||||||
const ship = this.scene && this.scene.ship;
|
|
||||||
if (
|
|
||||||
ship &&
|
|
||||||
ship.state !== 'mining' &&
|
|
||||||
(this.state === 'extending' || this.state === 'mining')
|
|
||||||
) {
|
|
||||||
this.stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.state === 'extending' && time >= this.stateT0 + this.armExtendMs) {
|
if (this.state === 'extending' && time >= this.stateT0 + this.armExtendMs) {
|
||||||
this.state = 'mining';
|
this.state = 'mining';
|
||||||
this.beam = new MiningBeam(this.scene, this);
|
this.beam = new MiningBeam(this.scene, this);
|
||||||
|
|
|
||||||
|
|
@ -318,11 +318,10 @@ export class GameScene extends Phaser.Scene {
|
||||||
|
|
||||||
// ---- MINING (the clusters are now verbs, not just rocks) -----------
|
// ---- MINING (the clusters are now verbs, not just rocks) -----------
|
||||||
// The state machine (js/mining/Mining.js): idle → extending (~1.5 s,
|
// The state machine (js/mining/Mining.js): idle → extending (~1.5 s,
|
||||||
// the ship holds station, "Extending Mining Arm..." in the console
|
// the ship is locked, "Extending Mining Arm..." in the console slot)
|
||||||
// slot) → mining (the beam is live — js/mining/MiningBeam.js)
|
// → mining (the beam is live — js/mining/MiningBeam.js) → retracting.
|
||||||
// → retracting. The pop-up (js/ui/MiningPopup.js) is the context
|
// The pop-up (js/ui/MiningPopup.js) is the context menu a rock click
|
||||||
// menu a rock click opens, world-anchored right where the player
|
// opens, world-anchored right where the player clicked.
|
||||||
// clicked.
|
|
||||||
this.mining = new Mining(this, {
|
this.mining = new Mining(this, {
|
||||||
onPhase: (p) => this.onMiningPhase(p),
|
onPhase: (p) => this.onMiningPhase(p),
|
||||||
});
|
});
|
||||||
|
|
@ -330,24 +329,14 @@ export class GameScene extends Phaser.Scene {
|
||||||
onAction: (id, rock) => this.miningAction(id, rock),
|
onAction: (id, rock) => this.miningAction(id, rock),
|
||||||
});
|
});
|
||||||
|
|
||||||
// SHIP STATE (js/entities/Ship.js): 'normal' is the default — the
|
|
||||||
// ship is free; 'mining' — the arm's sequence owns the ship. The
|
|
||||||
// mining visuals live and die with that state: ANY change out of it
|
|
||||||
// (the player moving the ship — a world click or the compass
|
|
||||||
// autopilot — a cancel, or a future state) ends the mining sequence.
|
|
||||||
this.ship.onStateChange = (next, prev) => {
|
|
||||||
if (prev === 'mining' && next !== 'mining') this.mining.stop();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Input: click = fly there. A click ON the command deck is deck
|
// Input: click = fly there. A click ON the command deck is deck
|
||||||
// business, a click on a compass name tag is an autopilot (it
|
// business, a click on a compass name tag is an autopilot (it
|
||||||
// already retargeted the ship), and a click on the system NAME (or
|
// already retargeted the ship), and a click on the system NAME (or
|
||||||
// the caret beside it) toggles the dossier open/closed — none of
|
// the caret beside it) toggles the dossier open/closed — none of
|
||||||
// those is a fly-here. A click on an ASTEROID opens the mining
|
// those is a fly-here. A click on an ASTEROID opens the mining
|
||||||
// pop-up (a context menu — its buttons own their clicks; any click
|
// pop-up (a context menu — its buttons own their clicks; any click
|
||||||
// elsewhere closes it and is consumed). Any OTHER click moves the
|
// elsewhere closes it and is consumed). While the beam is live the
|
||||||
// ship — which ends the mining state (the beam retracts as the
|
// ship is LOCKED: a world click breaks the beam first, then flies.
|
||||||
// ship goes; a mid-reach arm aborts).
|
|
||||||
// A click inside a planet clamps to that planet's keep-out rim — the
|
// A click inside a planet clamps to that planet's keep-out rim — the
|
||||||
// ship can stop at the clearance, never inside. (Worlds don't
|
// ship can stop at the clearance, never inside. (Worlds don't
|
||||||
// overlap, so sequential clamping is exact.) A click BEYOND the
|
// overlap, so sequential clamping is exact.) A click BEYOND the
|
||||||
|
|
@ -388,20 +377,19 @@ export class GameScene extends Phaser.Scene {
|
||||||
|
|
||||||
// A click the pop-up's OWN buttons just handled (they close it from
|
// A click the pop-up's OWN buttons just handled (they close it from
|
||||||
// their side first — the two handlers race on event order): it is
|
// their side first — the two handlers race on event order): it is
|
||||||
// the menu's click, never a fly-here. The button and this handler
|
// the menu's click, never a fly-here.
|
||||||
// fire on the SAME input pass (same frame — this.time.now is
|
|
||||||
// identical), so "same frame AND inside the panel's footprint"
|
|
||||||
// isolates that exact click without swallowing the player's next
|
|
||||||
// deliberate one (a click on the rock, to re-open the menu).
|
|
||||||
if (
|
if (
|
||||||
this.miningPopup &&
|
this.miningPopup &&
|
||||||
this.miningPopup.closedByButtonAt !== null &&
|
this.miningPopup.closedByButtonAt !== null &&
|
||||||
this.time.now - this.miningPopup.closedByButtonAt < 20 &&
|
this.time.now - this.miningPopup.closedByButtonAt < 300
|
||||||
this.miningPopup.containsScreen(pointer.x, pointer.y)
|
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The arm is mid-extension: the ship is committed to the rock for
|
||||||
|
// the ~1.5 s reach — the world holds (the toast says why).
|
||||||
|
if (this.mining.state === 'extending') return;
|
||||||
|
|
||||||
// A rock under the cursor: clicking an asteroid opens the mining
|
// A rock under the cursor: clicking an asteroid opens the mining
|
||||||
// menu (or the stop menu, when this cluster is the beam's target)
|
// menu (or the stop menu, when this cluster is the beam's target)
|
||||||
// — this click does not fly the ship.
|
// — this click does not fly the ship.
|
||||||
|
|
@ -411,10 +399,9 @@ export class GameScene extends Phaser.Scene {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Any other click MOVES the ship — which ends the mining state
|
// Beam live and the click is OFF the rock: break the beam (it
|
||||||
// (the beam retracts as the ship goes; a mid-reach arm aborts).
|
// retracts while the ship goes) and fly to the click.
|
||||||
// The state exit is signalled via ship.onStateChange above.
|
if (this.mining.state === 'mining') this.mining.stop();
|
||||||
if (this.mining.isActive) this.mining.stop();
|
|
||||||
|
|
||||||
let aim = { x: pointer.worldX, y: pointer.worldY };
|
let aim = { x: pointer.worldX, y: pointer.worldY };
|
||||||
for (const s of this.solids) {
|
for (const s of this.solids) {
|
||||||
|
|
@ -1063,12 +1050,11 @@ export class GameScene extends Phaser.Scene {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mining phase changes (Mining → onPhase): the scene's share of the
|
* Mining phase changes (Mining → onPhase): the scene's share of the
|
||||||
* sequence — the ship's STATE, the ship lock, the console calls, the sfx.
|
* sequence — the ship lock, the console calls, the sfx.
|
||||||
*/
|
*/
|
||||||
onMiningPhase(phase) {
|
onMiningPhase(phase) {
|
||||||
if (phase === 'extending') {
|
if (phase === 'extending') {
|
||||||
this.ship.stop(); // hold station — the arm needs a steady hull
|
this.ship.stop(); // hold station — the arm needs a steady hull
|
||||||
this.ship.setState('mining', 'mining'); // the arm's sequence owns the ship
|
|
||||||
this.playSfx('mining'); // the arm powers up
|
this.playSfx('mining'); // the arm powers up
|
||||||
this.hideHint();
|
this.hideHint();
|
||||||
// The console call, in the same slot as the discovery toasts —
|
// The console call, in the same slot as the discovery toasts —
|
||||||
|
|
@ -1080,9 +1066,6 @@ export class GameScene extends Phaser.Scene {
|
||||||
});
|
});
|
||||||
} else if (phase === 'stopped') {
|
} else if (phase === 'stopped') {
|
||||||
this.playSfx('deconstruct'); // the arm pulls back
|
this.playSfx('deconstruct'); // the arm pulls back
|
||||||
// Back to normal — a no-op when the ship already left 'mining'
|
|
||||||
// itself (the player moved it, which ended the sequence).
|
|
||||||
this.ship.setState('normal', 'mining-ended');
|
|
||||||
}
|
}
|
||||||
// 'mining' = the beam is live — the visual speaks for itself.
|
// 'mining' = the beam is live — the visual speaks for itself.
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -259,18 +259,6 @@ export class MiningPopup extends Phaser.GameObjects.Container {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Does a SCREEN point (canvas coords, top-left origin) sit inside the
|
|
||||||
* panel's current footprint — buttons included? GameScene uses this to
|
|
||||||
* swallow the very click that just closed the panel through a button:
|
|
||||||
* that click landed ON the panel, so it was panel business, not a
|
|
||||||
* world click. Position-based, no timing.
|
|
||||||
*/
|
|
||||||
containsScreen(sx, sy) {
|
|
||||||
const cam = this.scene.cameras.main;
|
|
||||||
return this.contains(cam.scrollX + sx, cam.scrollY + sy);
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.panelG?.destroy();
|
this.panelG?.destroy();
|
||||||
this.headerText?.destroy();
|
this.headerText?.destroy();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue