orbit/js/mining/Mining.js

142 lines
5.0 KiB
JavaScript

import { config } from '../config/Config.js';
import { MiningBeam } from './MiningBeam.js';
/**
* The ship's mining state — the machine behind the beam. The SHIP'S
* 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
* ▲ │ ▲ │ │
* │ └──┘ (stop() cuts the extension) └────beam out────┘
* └──────────────────────────────────────────────────────────────────────┘
*
* extending the arm is reaching for the rock — the ship holds
* station (GameScene stops it) and sits in 'mining';
* mining the beam is live (MiningBeam) — the ship stays at its
* rim, still 'mining'; retargeting (begin on another rock)
* cuts the current beam and re-extends;
* retracting the beam is pulling back rock → ship; the ship is already
* BACK TO 'normal' (FREE) — the player can fly as it dies.
*
* This class owns only the sequence + the beam's lifecycle. Scene
* concerns (ship stop, toasts, sfx) ride the onPhase seam — the same
* pattern as TetherField's onChange.
*/
export class Mining {
/**
* @param {Phaser.Scene} scene
* @param {object} [o]
* @param {Function} [o.onPhase] — (phase) => void, phase ∈ 'extending' | 'mining' | 'stopped'
*/
constructor(scene, o = {}) {
this.scene = scene;
this.onPhase = typeof o.onPhase === 'function' ? o.onPhase : null;
this.state = 'idle'; // 'idle' | 'extending' | 'mining' | 'retracting'
this.cluster = null;
this.member = null;
this.beam = null;
this.stateT0 = 0;
this.armExtendMs = config.get('asteroids.mining.armExtendMs', 1500);
}
/** The arm's sequence is live: mid-extension OR beam live. */
get isActive() {
return this.state === 'extending' || this.state === 'mining';
}
/**
* Start mining `cluster`'s rock `member` (from the pop-up's
* "Mine Asteroids"). Cuts any live/retracting beam first (retarget).
* Returns false if already extending — the arm is committed.
*/
begin(cluster, member) {
if (this.state === 'extending') return false;
if (this.state === 'mining' || this.state === 'retracting') {
this.beam?.destroy();
this.beam = null;
}
this.cluster = cluster;
this.member = member;
this.state = 'extending';
this.stateT0 = this.scene.time.now;
this._phase('extending');
return true;
}
/**
* Stop mining (the pop-up's "Stop Mining", a world click, or ESC):
* mid-extension → abort; beam live → it retracts (retracting).
*/
stop() {
if (this.state === 'extending') {
this.state = 'idle';
this._phase('stopped');
return;
}
if (this.state === 'mining' && this.beam && !this.beam.outRunning) {
this.state = 'retracting';
this.beam.beginOut(this.scene.time.now);
}
}
/**
* Per-frame (GameScene.update, after the clusters have refreshed their
* rock positions — the beam tracks them). Drives the extending →
* mining hand-off and reaps a finished retract.
*/
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) {
this.state = 'mining';
this.beam = new MiningBeam(this.scene, this);
this._phase('mining');
}
if (this.beam) {
this.beam.update(time, delta);
if (this.beam.finished) {
this.beam.destroy();
this.beam = null;
if (this.state === 'retracting') {
this.state = 'idle';
this._phase('stopped');
}
}
}
}
_phase(phase) {
try {
this.onPhase?.(phase);
} catch (err) {
console.error('[mining] onPhase handler failed', err);
}
}
destroy() {
this.beam?.destroy();
this.beam = null;
this.state = 'idle';
this.cluster = null;
this.member = null;
this.onPhase = null;
}
}