Compare commits

...

2 Commits

Author SHA1 Message Date
65e7c496b7 Add ShipStatus system with automatic gauge updates and fuel consumption
All checks were successful
Build / build (push) Successful in 1m26s
Implemented a comprehensive ship status management system with event-driven gauge updates and integrated fuel consumption for both linear and angular thrust.

**New File: src/shipStatus.ts**
- ShipStatus class with Observable pattern for status change events
- Manages fuel, hull, and ammo values with automatic clamping (0-1 range)
- Configurable max values for each resource type
- Public getters: fuel, hull, ammo, getValues()
- Setter methods: setFuel(), setHull(), setAmmo() with automatic event firing
- Convenience methods: addFuel(), consumeFuel(), damageHull(), repairHull(), addAmmo(), consumeAmmo()
- Status check methods: isFuelEmpty(), isDestroyed(), isAmmoEmpty()
- Utility methods: reset(), setMaxValues(), dispose()
- ShipStatusChangeEvent interface with statusType, oldValue, newValue, delta fields

**Modified: src/scoreboard.ts**
- Integrated ShipStatus instance as private _shipStatus
- Constructor subscribes to ShipStatus.onStatusChanged observable
- Added public shipStatus getter to expose status manager
- Created createGaugesDisplay() method with 3 bar gauges (FUEL, HULL, AMMO)
- Created createGaugeBar() helper for individual gauge construction
- Added getBarColor() with smooth RGB gradient: green (1.0) -> yellow (0.5) -> red (0.0)
- Renamed public methods to private: updateFuelBar(), updateHullBar(), updateAmmoBar()
- Observable subscription automatically updates gauge visuals when status changes
- Added dispose() method for cleanup of ShipStatus and observables
- Updated initialize() to retrieve and setup screen/gauges meshes from GLB
- Set initial test values to full (1.0) for all gauges

**Modified: src/shipPhysics.ts**
- Added ShipStatus import and private _shipStatus property
- Added setShipStatus() method to connect status manager
- Modified applyForces() to check fuel availability before applying linear force
- Linear thrust fuel consumption: linearMagnitude (0-1) * 0.005 per frame
- Added fuel check and consumption for angular thrust (rotation)
- Angular thrust fuel consumption: normalized angularMagnitude (0-1) * 0.005 per frame
- Forces only applied when fuel > 0

**Modified: src/ship.ts**
- Connected ShipPhysics to Scoreboard's ShipStatus via setShipStatus()
- Called immediately after physics initialization (line 148)

This creates a fully integrated system where:
1. Ship movement (linear and angular) consumes fuel proportional to thrust
2. Fuel depletion prevents further thrust application
3. Gauge displays automatically update via observable events with color coding
4. Other systems can monitor/modify ship status through the same interface

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-07 15:17:49 -06:00
2e1c8ad725 Fix inspector 'i' key registration in KeyboardInput
Problem:
- main.ts registered inspector with window.addEventListener
- keyboardInput.ts used document.onkeydown which replaces event handler
- This caused the inspector key binding to be overridden

Solution:
- Moved inspector 'i' key handling into KeyboardInput class
- Removed duplicate setupInspector() method from main.ts
- Inspector now opens correctly when 'i' is pressed

Changes:
- src/keyboardInput.ts: Added 'i' key case to open Babylon Inspector
- src/main.ts: Removed setupInspector() method (no longer needed)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-07 13:07:15 -06:00
7 changed files with 516 additions and 73 deletions

View File

@ -62,6 +62,15 @@ export class KeyboardInput {
document.onkeydown = (ev) => { document.onkeydown = (ev) => {
switch (ev.key) { switch (ev.key) {
case 'i':
// Open Babylon Inspector
import("@babylonjs/inspector").then((inspector) => {
inspector.Inspector.Show(this._scene, {
overlay: true,
showExplorer: true,
});
});
break;
case '1': case '1':
this._onCameraChangeObservable.notifyObservers(1); this._onCameraChangeObservable.notifyObservers(1);
break; break;

View File

@ -256,7 +256,6 @@ export class Main {
this._audioEngine = await CreateAudioEngineAsync(); this._audioEngine = await CreateAudioEngineAsync();
this.setupInspector();
window.setTimeout(()=>{ window.setTimeout(()=>{
if (!this._started) { if (!this._started) {
this._started = true; this._started = true;
@ -286,20 +285,6 @@ export class Main {
DefaultScene.MainScene.collisionsEnabled = true; DefaultScene.MainScene.collisionsEnabled = true;
} }
private setupInspector() {
setLoadingMessage("Initializing Inspector...");
window.addEventListener("keydown", (ev) => {
if (ev.key == 'i') {
import ("@babylonjs/inspector").then((inspector) => {
inspector.Inspector.Show(DefaultScene.MainScene, {
overlay: true,
showExplorer: true
});
});
}
});
}
} }
// Setup router // Setup router

View File

@ -1,12 +1,14 @@
import {AdvancedDynamicTexture, Control, StackPanel, TextBlock} from "@babylonjs/gui"; import {AdvancedDynamicTexture, Control, StackPanel, TextBlock, Rectangle, Container} from "@babylonjs/gui";
import {DefaultScene} from "./defaultScene"; import {DefaultScene} from "./defaultScene";
import { import {
AbstractMesh, Mesh, Mesh,
MeshBuilder, MeshBuilder,
Observable, StandardMaterial, Observable,
Vector3, Vector3,
} from "@babylonjs/core"; } from "@babylonjs/core";
import debugLog from './debug'; import debugLog from './debug';
import { ShipStatus } from './shipStatus';
export type ScoreEvent = { export type ScoreEvent = {
score: number, score: number,
message: string, message: string,
@ -21,8 +23,32 @@ export class Scoreboard {
private _active = false; private _active = false;
private _done = false; private _done = false;
public readonly onScoreObservable: Observable<ScoreEvent> = new Observable<ScoreEvent>(); public readonly onScoreObservable: Observable<ScoreEvent> = new Observable<ScoreEvent>();
constructor() {
// Gauge bar fill rectangles
private _fuelBar: Rectangle | null = null;
private _hullBar: Rectangle | null = null;
private _ammoBar: Rectangle | null = null;
// Ship status manager
private _shipStatus: ShipStatus;
constructor() {
this._shipStatus = new ShipStatus();
// Subscribe to status changes to automatically update gauges
this._shipStatus.onStatusChanged.add((event) => {
switch (event.statusType) {
case 'fuel':
this.updateFuelBar(event.newValue);
break;
case 'hull':
this.updateHullBar(event.newValue);
break;
case 'ammo':
this.updateAmmoBar(event.newValue);
break;
}
});
} }
public get done() { public get done() {
return this._done; return this._done;
@ -30,26 +56,60 @@ export class Scoreboard {
public set done(value: boolean) { public set done(value: boolean) {
this._done = value; this._done = value;
} }
/**
* Get the ship status manager
*/
public get shipStatus(): ShipStatus {
return this._shipStatus;
}
public setRemainingCount(count: number) { public setRemainingCount(count: number) {
this._remaining = count; this._remaining = count;
} }
public initialize(baseMesh: Mesh) { public initialize(): void {
const scene = DefaultScene.MainScene; const scene = DefaultScene.MainScene;
const parent = scene.getNodeById('ship'); const parent = scene.getNodeById('ship');
debugLog('Scoreboard parent:', parent); debugLog('Scoreboard parent:', parent);
debugLog('Initializing scoreboard'); debugLog('Initializing scoreboard');
let scoreboard = null; let scoreboard: Mesh | null = null;
if (baseMesh) { // Retrieve and setup screen mesh from the loaded GLB
scoreboard = baseMesh; const screen = scene.getMaterialById("Screen")?.getBindedMeshes()[0] as Mesh;
if (screen) {
// Setup screen mesh: adjust pivot point and rotation
const oldParent = screen.parent;
screen.setParent(null);
screen.setPivotPoint(screen.getBoundingInfo().boundingSphere.center);
screen.setParent(oldParent);
screen.rotation.y = Math.PI;
scoreboard = screen;
scoreboard.material.dispose(); scoreboard.material.dispose();
//scoreboard.material = new StandardMaterial("scoreboard", scene); }
} else { // Retrieve and setup gauges mesh from the loaded GLB
const gauges = scene.getMaterialById("Gauges")?.getBindedMeshes()[0] as Mesh;
if (gauges) {
// Setup gauges mesh: adjust pivot point and rotation
const oldParent = gauges.parent;
gauges.setParent(null);
gauges.setPivotPoint(gauges.getBoundingInfo().boundingSphere.center);
gauges.setParent(oldParent);
//gauges.rotation.z = Math.PI;
// Create gauges display
this.createGaugesDisplay(gauges);
}
// Fallback: create a plane if screen mesh not found
if (!scoreboard) {
console.error('Screen mesh not found, creating fallback plane');
scoreboard = MeshBuilder.CreatePlane("scoreboard", {width: 1, height: 1}, scene); scoreboard = MeshBuilder.CreatePlane("scoreboard", {width: 1, height: 1}, scene);
scoreboard.parent =parent; scoreboard.parent = parent;
scoreboard.position.y = 1.05; scoreboard.position.y = 1.05;
scoreboard.position.z = 2.1; scoreboard.position.z = 2.1;
@ -66,7 +126,7 @@ export class Scoreboard {
const advancedTexture = AdvancedDynamicTexture.CreateForMesh(scoreboard, 512, 512); const advancedTexture = AdvancedDynamicTexture.CreateForMesh(scoreboard, 512, 512);
advancedTexture.background = "green"; advancedTexture.background = "black";
advancedTexture.hasAlpha = false; advancedTexture.hasAlpha = false;
const scoreText = this.createText(); const scoreText = this.createText();
@ -122,4 +182,171 @@ export class Scoreboard {
private calculateScore() { private calculateScore() {
return Math.floor(this._score); return Math.floor(this._score);
} }
/**
* Create the gauges display with 3 bar gauges (Fuel, Hull, Ammo)
*/
private createGaugesDisplay(gaugesMesh: Mesh): void {
// Store reference to old material to dispose after new one is created
const oldMaterial = gaugesMesh.material;
// Create AdvancedDynamicTexture for the gauges mesh
// This creates a new StandardMaterial and assigns it to the mesh
const gaugesTexture = AdvancedDynamicTexture.CreateForMesh(gaugesMesh, 512, 512);
gaugesTexture.coordinatesIndex = 2;
gaugesTexture.background = "#444444";
gaugesTexture.hasAlpha = false;
// Now dispose the old material after the new one is assigned
if (oldMaterial) {
oldMaterial.dispose(true, true);
}
debugLog('Gauges texture created, material:', gaugesMesh.material?.name);
// Create a vertical stack panel for the gauges
const panel = new StackPanel('GaugesPanel');
panel.rotation = Math.PI;
panel.isVertical = true;
panel.width = "100%";
panel.height = "100%";
// Create the three gauges
this._fuelBar = this.createGaugeBar("FUEL", "#00FF00", panel);
this._hullBar = this.createGaugeBar("HULL", "#00FF00", panel);
this._ammoBar = this.createGaugeBar("AMMO", "#00FF00", panel);
gaugesTexture.addControl(panel);
let i = 0;
// Force the texture to update
//gaugesTexture.markAsDirty();
// Set initial values to full (for testing visibility)
this._shipStatus.setFuel(1);
this._shipStatus.setHull(1);
this._shipStatus.setAmmo(1);
debugLog('Gauges display created with initial test values');
}
/**
* Create a single gauge bar with label
*/
private createGaugeBar(label: string, color: string, parent: Container): Rectangle {
// Container for this gauge (label + bar)
const gaugeContainer = new StackPanel();
gaugeContainer.isVertical = true;
gaugeContainer.height = "140px";
gaugeContainer.width = "100%";
gaugeContainer.paddingBottom = "15px";
// Label text
const labelText = new TextBlock();
labelText.text = label;
labelText.color = "#FFFFFF";
labelText.fontSize = "60px";
labelText.height = "70px";
labelText.textHorizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
gaugeContainer.addControl(labelText);
// Bar background (border and empty space)
const barBackground = new Rectangle();
barBackground.height = "50px";
barBackground.width = "100%";
barBackground.thickness = 3;
barBackground.color = "#FFFFFF";
barBackground.background = "#333333";
barBackground.cornerRadius = 5;
// Bar fill (the actual gauge)
const barFill = new Rectangle();
barFill.height = "100%";
barFill.width = "100%"; // Will be updated dynamically
barFill.thickness = 0;
barFill.background = color;
barFill.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
barFill.cornerRadius = 3;
barBackground.addControl(barFill);
gaugeContainer.addControl(barBackground);
parent.addControl(gaugeContainer);
return barFill;
}
/**
* Get bar color based on value with smooth gradient
* Green (1.0) -> Yellow (0.5) -> Red (0.0)
*/
private getBarColor(value: number): string {
// Clamp value between 0 and 1
value = Math.max(0, Math.min(1, value));
let red: number, green: number;
if (value >= 0.5) {
// Interpolate from yellow (0.5) to green (1.0)
// At 0.5: RGB(255, 255, 0) yellow
// At 1.0: RGB(0, 255, 0) green
const t = (value - 0.5) * 2; // 0 to 1 range
red = Math.round(255 * (1 - t));
green = 255;
} else {
// Interpolate from red (0.0) to yellow (0.5)
// At 0.0: RGB(255, 0, 0) red
// At 0.5: RGB(255, 255, 0) yellow
const t = value * 2; // 0 to 1 range
red = 255;
green = Math.round(255 * t);
}
// Convert to hex
const redHex = red.toString(16).padStart(2, '0');
const greenHex = green.toString(16).padStart(2, '0');
return `#${redHex}${greenHex}00`;
}
/**
* Internal method to update fuel gauge bar
*/
private updateFuelBar(value: number): void {
if (this._fuelBar) {
this._fuelBar.width = `${value * 100}%`;
this._fuelBar.background = this.getBarColor(value);
}
}
/**
* Internal method to update hull gauge bar
*/
private updateHullBar(value: number): void {
if (this._hullBar) {
this._hullBar.width = `${value * 100}%`;
this._hullBar.background = this.getBarColor(value);
}
}
/**
* Internal method to update ammo gauge bar
*/
private updateAmmoBar(value: number): void {
if (this._ammoBar) {
this._ammoBar.width = `${value * 100}%`;
this._ammoBar.background = this.getBarColor(value);
}
}
/**
* Dispose of scoreboard resources
*/
public dispose(): void {
if (this._shipStatus) {
this._shipStatus.dispose();
}
this.onScoreObservable.clear();
}
} }

View File

@ -145,6 +145,7 @@ export class Ship {
// Initialize physics controller // Initialize physics controller
this._physics = new ShipPhysics(); this._physics = new ShipPhysics();
this._physics.setShipStatus(this._scoreboard.shipStatus);
// Setup physics update loop (every 10 frames) // Setup physics update loop (every 10 frames)
DefaultScene.MainScene.onAfterRenderObservable.add(() => { DefaultScene.MainScene.onAfterRenderObservable.add(() => {
@ -174,21 +175,8 @@ export class Ship {
centerGap: 0.5, centerGap: 0.5,
}); });
// Setup scoreboard on screen // Initialize scoreboard (it will retrieve and setup its own screen mesh)
console.log(data.meshes.get("Screen")); this._scoreboard.initialize();
const screen = DefaultScene.MainScene
.getMaterialById("Screen")
.getBindedMeshes()[0] as AbstractMesh;
console.log(screen);
const old = screen.parent;
screen.setParent(null);
screen.setPivotPoint(screen.getBoundingInfo().boundingSphere.center);
screen.setParent(old);
screen.rotation.y = Math.PI;
console.log(screen.rotation);
console.log(screen.scaling);
this._scoreboard.initialize(screen as any);
} }
/** /**

View File

@ -1,5 +1,6 @@
import { PhysicsBody, TransformNode, Vector2, Vector3 } from "@babylonjs/core"; import { PhysicsBody, TransformNode, Vector2, Vector3 } from "@babylonjs/core";
import { GameConfig } from "./gameConfig"; import { GameConfig } from "./gameConfig";
import { ShipStatus } from "./shipStatus";
export interface InputState { export interface InputState {
leftStick: Vector2; leftStick: Vector2;
@ -16,6 +17,14 @@ export interface ForceApplicationResult {
* Reads physics parameters from GameConfig for runtime tuning * Reads physics parameters from GameConfig for runtime tuning
*/ */
export class ShipPhysics { export class ShipPhysics {
private _shipStatus: ShipStatus | null = null;
/**
* Set the ship status instance for fuel consumption tracking
*/
public setShipStatus(shipStatus: ShipStatus): void {
this._shipStatus = shipStatus;
}
/** /**
* Apply forces to the ship based on input state * Apply forces to the ship based on input state
* @param inputState - Current input state (stick positions) * @param inputState - Current input state (stick positions)
@ -49,24 +58,31 @@ export class ShipPhysics {
if (Math.abs(leftStick.y) > 0.1) { if (Math.abs(leftStick.y) > 0.1) {
linearMagnitude = Math.abs(leftStick.y); linearMagnitude = Math.abs(leftStick.y);
// Only apply force if we haven't reached max velocity // Check if we have fuel before applying force
if (currentSpeed < config.maxLinearVelocity) { if (this._shipStatus && this._shipStatus.fuel > 0) {
// Get local direction (Z-axis for forward/backward thrust) // Only apply force if we haven't reached max velocity
const localDirection = new Vector3(0, 0, -leftStick.y); if (currentSpeed < config.maxLinearVelocity) {
// Transform to world space // Get local direction (Z-axis for forward/backward thrust)
const worldDirection = Vector3.TransformNormal( const localDirection = new Vector3(0, 0, -leftStick.y);
localDirection, // Transform to world space
transformNode.getWorldMatrix() const worldDirection = Vector3.TransformNormal(
); localDirection,
const force = worldDirection.scale(config.linearForceMultiplier); transformNode.getWorldMatrix()
);
const force = worldDirection.scale(config.linearForceMultiplier);
// Calculate thrust point: center of mass + offset (0, 1, 0) in world space // Calculate thrust point: center of mass + offset (0, 1, 0) in world space
const thrustPoint = Vector3.TransformCoordinates( const thrustPoint = Vector3.TransformCoordinates(
physicsBody.getMassProperties().centerOfMass.add(new Vector3(0, 1, 0)), physicsBody.getMassProperties().centerOfMass.add(new Vector3(0, 1, 0)),
transformNode.getWorldMatrix() transformNode.getWorldMatrix()
); );
physicsBody.applyForce(force, thrustPoint); physicsBody.applyForce(force, thrustPoint);
// Consume fuel: normalized magnitude (0-1) * 0.01 = max consumption of 0.01 per frame
const fuelConsumption = linearMagnitude * 0.005;
this._shipStatus.consumeFuel(fuelConsumption);
}
} }
} }
@ -78,24 +94,32 @@ export class ShipPhysics {
// Apply angular forces if any stick has significant rotation input // Apply angular forces if any stick has significant rotation input
if (angularMagnitude > 0.1) { if (angularMagnitude > 0.1) {
const currentAngularSpeed = currentAngularVelocity.length(); // Check if we have fuel before applying torque
if (this._shipStatus && this._shipStatus.fuel > 0) {
const currentAngularSpeed = currentAngularVelocity.length();
// Only apply torque if we haven't reached max angular velocity // Only apply torque if we haven't reached max angular velocity
if (currentAngularSpeed < config.maxAngularVelocity) { if (currentAngularSpeed < config.maxAngularVelocity) {
const yaw = -leftStick.x; const yaw = -leftStick.x;
const pitch = rightStick.y; const pitch = rightStick.y;
const roll = rightStick.x; const roll = rightStick.x;
// Create torque in local space, then transform to world space // Create torque in local space, then transform to world space
const localTorque = new Vector3(pitch, yaw, roll).scale( const localTorque = new Vector3(pitch, yaw, roll).scale(
config.angularForceMultiplier config.angularForceMultiplier
); );
const worldTorque = Vector3.TransformNormal( const worldTorque = Vector3.TransformNormal(
localTorque, localTorque,
transformNode.getWorldMatrix() transformNode.getWorldMatrix()
); );
physicsBody.applyAngularImpulse(worldTorque); physicsBody.applyAngularImpulse(worldTorque);
// Consume fuel: normalized magnitude (0-3 max) / 3 * 0.01 = max consumption of 0.01 per frame
const normalizedAngularMagnitude = Math.min(angularMagnitude / 3.0, 1.0);
const fuelConsumption = normalizedAngularMagnitude * 0.005;
this._shipStatus.consumeFuel(fuelConsumption);
}
} }
} }

210
src/shipStatus.ts Normal file
View File

@ -0,0 +1,210 @@
import { Observable } from "@babylonjs/core";
/**
* Event data for ship status changes
*/
export interface ShipStatusChangeEvent {
statusType: "fuel" | "hull" | "ammo";
oldValue: number;
newValue: number;
delta: number;
}
/**
* Ship status values container
*/
export interface ShipStatusValues {
fuel: number;
hull: number;
ammo: number;
}
/**
* Manages ship status values (fuel, hull integrity, ammo)
* Provides observable events for changes and automatic clamping to 0-1 range
*/
export class ShipStatus {
private _fuel: number = 1.0;
private _hull: number = 1.0;
private _ammo: number = 1.0;
// Maximum values for each resource
private _maxFuel: number = 1.0;
private _maxHull: number = 1.0;
private _maxAmmo: number = 1.0;
// Observable for status changes
public readonly onStatusChanged: Observable<ShipStatusChangeEvent> =
new Observable<ShipStatusChangeEvent>();
/**
* Get current fuel level (0-1)
*/
public get fuel(): number {
return this._fuel;
}
/**
* Get current hull integrity (0-1)
*/
public get hull(): number {
return this._hull;
}
/**
* Get current ammo level (0-1)
*/
public get ammo(): number {
return this._ammo;
}
/**
* Get all status values
*/
public getValues(): ShipStatusValues {
return {
fuel: this._fuel,
hull: this._hull,
ammo: this._ammo,
};
}
/**
* Set fuel level directly (clamped to 0-1)
*/
public setFuel(value: number): void {
const oldValue = this._fuel;
this._fuel = Math.max(0, Math.min(this._maxFuel, value));
if (oldValue !== this._fuel) {
this.onStatusChanged.notifyObservers({
statusType: "fuel",
oldValue,
newValue: this._fuel,
delta: this._fuel - oldValue,
});
}
}
/**
* Set hull integrity directly (clamped to 0-1)
*/
public setHull(value: number): void {
const oldValue = this._hull;
this._hull = Math.max(0, Math.min(this._maxHull, value));
if (oldValue !== this._hull) {
this.onStatusChanged.notifyObservers({
statusType: "hull",
oldValue,
newValue: this._hull,
delta: this._hull - oldValue,
});
}
}
/**
* Set ammo level directly (clamped to 0-1)
*/
public setAmmo(value: number): void {
const oldValue = this._ammo;
this._ammo = Math.max(0, Math.min(this._maxAmmo, value));
if (oldValue !== this._ammo) {
this.onStatusChanged.notifyObservers({
statusType: "ammo",
oldValue,
newValue: this._ammo,
delta: this._ammo - oldValue,
});
}
}
/**
* Increment fuel by delta amount
*/
public addFuel(delta: number): void {
this.setFuel(this._fuel + delta);
}
/**
* Decrement fuel by delta amount
*/
public consumeFuel(delta: number): void {
this.setFuel(this._fuel - delta);
}
/**
* Damage hull by delta amount
*/
public damageHull(delta: number): void {
this.setHull(this._hull - delta);
}
/**
* Repair hull by delta amount
*/
public repairHull(delta: number): void {
this.setHull(this._hull + delta);
}
/**
* Increment ammo by delta amount
*/
public addAmmo(delta: number): void {
this.setAmmo(this._ammo + delta);
}
/**
* Decrement ammo by delta amount (fire weapon)
*/
public consumeAmmo(delta: number): void {
this.setAmmo(this._ammo - delta);
}
/**
* Check if fuel is depleted
*/
public isFuelEmpty(): boolean {
return this._fuel <= 0;
}
/**
* Check if hull is destroyed
*/
public isDestroyed(): boolean {
return this._hull <= 0;
}
/**
* Check if ammo is depleted
*/
public isAmmoEmpty(): boolean {
return this._ammo <= 0;
}
/**
* Reset all values to full
*/
public reset(): void {
this.setFuel(this._maxFuel);
this.setHull(this._maxHull);
this.setAmmo(this._maxAmmo);
}
/**
* Set maximum values for resources
*/
public setMaxValues(fuel?: number, hull?: number, ammo?: number): void {
if (fuel !== undefined) this._maxFuel = fuel;
if (hull !== undefined) this._maxHull = hull;
if (ammo !== undefined) this._maxAmmo = ammo;
}
/**
* Dispose observables
*/
public dispose(): void {
this.onStatusChanged.clear();
}
}