Add resupply system for base landing zone
Some checks failed
Build / build (push) Failing after 19s

Implemented automatic resource replenishment when the ship is inside the base landing zone.

**Modified: src/starBase.ts**
- Added StarBaseResult interface with baseMesh and landingAggregate properties
- Changed buildStarBase() return type from AbstractMesh to StarBaseResult
- Now returns both the base mesh and landing aggregate for resupply system access
- Landing aggregate already configured as trigger for collision detection

**Modified: src/levelDeserializer.ts**
- Added PhysicsAggregate import
- Updated deserialize() return type to include landingAggregate field
- Changed createStartBase() to return full StarBaseResult
- Updated return statement to destructure baseResult into baseMesh and landingAggregate

**Modified: src/level1.ts**
- Added PhysicsAggregate import and _landingAggregate property
- Stored landingAggregate from deserializer result
- Called ship.setLandingZone() to configure resupply system

**Modified: src/ship.ts**
- Added resupply system properties: _landingAggregate, _resupplyTimer, _isInLandingZone
- Added setLandingZone() method to configure landing zone for resupply
- Added updateResupply() method called every physics update (6 times per second)
- Distance-based detection: checks if ship is within 20 units of landing zone center
- Resupply rate: 0.1 per second for all resources (fuel, hull, ammo)
- Automatically replenishes until resources reach 1.0 maximum
- Debug logging for enter/exit landing zone events

Resupply System Mechanics:
- Activates when ship is within landing zone (distance < 20 units)
- Replenishes at 0.1 per second (~0.01666 per update at 6 updates/second)
- Repairs all three resources simultaneously: fuel, hull, ammo
- Stops automatically when each resource reaches maximum (1.0)
- Integrated with existing ShipStatus observable system for automatic gauge updates

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Michael Mainguy 2025-11-07 15:39:40 -06:00
parent 8605946fab
commit 827dd2d359
4 changed files with 102 additions and 9 deletions

View File

@ -3,6 +3,7 @@ import type {AudioEngineV2} from "@babylonjs/core";
import { import {
AbstractMesh, AbstractMesh,
Observable, Observable,
PhysicsAggregate,
Vector3 Vector3
} from "@babylonjs/core"; } from "@babylonjs/core";
import {Ship} from "./ship"; import {Ship} from "./ship";
@ -18,6 +19,7 @@ export class Level1 implements Level {
private _onReadyObservable: Observable<Level> = new Observable<Level>(); private _onReadyObservable: Observable<Level> = new Observable<Level>();
private _initialized: boolean = false; private _initialized: boolean = false;
private _startBase: AbstractMesh | null; private _startBase: AbstractMesh | null;
private _landingAggregate: PhysicsAggregate | null;
private _endBase: AbstractMesh; private _endBase: AbstractMesh;
private _levelConfig: LevelConfig; private _levelConfig: LevelConfig;
private _audioEngine: AudioEngineV2; private _audioEngine: AudioEngineV2;
@ -103,6 +105,13 @@ export class Level1 implements Level {
const entities = await this._deserializer.deserialize(this._ship.scoreboard.onScoreObservable); const entities = await this._deserializer.deserialize(this._ship.scoreboard.onScoreObservable);
this._startBase = entities.startBase; this._startBase = entities.startBase;
this._landingAggregate = entities.landingAggregate;
// Setup resupply system if landing aggregate exists
if (this._landingAggregate) {
this._ship.setLandingZone(this._landingAggregate);
}
// sun and planets are already created by deserializer // sun and planets are already created by deserializer
// Initialize scoreboard with total asteroid count // Initialize scoreboard with total asteroid count

View File

@ -3,6 +3,7 @@ import {
MeshBuilder, MeshBuilder,
Observable, Observable,
PBRMaterial, PBRMaterial,
PhysicsAggregate,
Texture, Texture,
Vector3 Vector3
} from "@babylonjs/core"; } from "@babylonjs/core";
@ -43,6 +44,7 @@ export class LevelDeserializer {
*/ */
public async deserialize(scoreObservable: Observable<ScoreEvent>): Promise<{ public async deserialize(scoreObservable: Observable<ScoreEvent>): Promise<{
startBase: AbstractMesh | null; startBase: AbstractMesh | null;
landingAggregate: PhysicsAggregate | null;
sun: AbstractMesh; sun: AbstractMesh;
planets: AbstractMesh[]; planets: AbstractMesh[];
asteroids: AbstractMesh[]; asteroids: AbstractMesh[];
@ -50,7 +52,7 @@ export class LevelDeserializer {
debugLog('Deserializing level:', this.config.difficulty); debugLog('Deserializing level:', this.config.difficulty);
// Create entities // Create entities
const startBase = await this.createStartBase(); const baseResult = await this.createStartBase();
const sun = this.createSun(); const sun = this.createSun();
const planets = this.createPlanets(); const planets = this.createPlanets();
const asteroids = await this.createAsteroids(scoreObservable); const asteroids = await this.createAsteroids(scoreObservable);
@ -63,7 +65,8 @@ export class LevelDeserializer {
light2.intensity = .5; light2.intensity = .5;
*/ */
return { return {
startBase, startBase: baseResult.baseMesh,
landingAggregate: baseResult.landingAggregate,
sun, sun,
planets, planets,
asteroids asteroids
@ -73,8 +76,8 @@ export class LevelDeserializer {
/** /**
* Create the start base from config * Create the start base from config
*/ */
private async createStartBase(): Promise<AbstractMesh> { private async createStartBase() {
return await StarBase.buildStarBase(); return await StarBase.buildStarBase();
} }
/** /**

View File

@ -42,6 +42,11 @@ export class Ship {
// Frame counter for physics updates // Frame counter for physics updates
private _frameCount: number = 0; private _frameCount: number = 0;
// Resupply system
private _landingAggregate: PhysicsAggregate | null = null;
private _resupplyTimer: number = 0;
private _isInLandingZone: boolean = false;
constructor(audioEngine?: AudioEngineV2) { constructor(audioEngine?: AudioEngineV2) {
this._audioEngine = audioEngine; this._audioEngine = audioEngine;
} }
@ -233,6 +238,58 @@ export class Ship {
forceMagnitudes.angularMagnitude forceMagnitudes.angularMagnitude
); );
} }
// Handle resupply when in landing zone
this.updateResupply();
}
/**
* Update resupply system - replenishes resources at 0.1 per second when in landing zone
*/
private updateResupply(): void {
if (!this._landingAggregate || !this._ship?.physicsBody) {
return;
}
// Check if ship is still in the landing zone by checking distance
// Since it's a trigger, we need to track position
const shipPos = this._ship.physicsBody.transformNode.position;
const landingPos = this._landingAggregate.transformNode.position;
const distance = Vector3.Distance(shipPos, landingPos);
// Assume landing zone radius is approximately 20 units (adjust as needed)
const wasInZone = this._isInLandingZone;
this._isInLandingZone = distance < 20;
if (this._isInLandingZone && !wasInZone) {
debugLog("Ship entered landing zone - resupply active");
} else if (!this._isInLandingZone && wasInZone) {
debugLog("Ship exited landing zone - resupply inactive");
}
// Resupply at 0.1 per second if in zone
if (this._isInLandingZone && this._scoreboard?.shipStatus) {
// Physics update runs every 10 frames at 60fps = 6 times per second
// 0.1 per second / 6 updates per second = 0.01666... per update
const resupplyRate = 0.1 / 6;
const status = this._scoreboard.shipStatus;
// Replenish fuel
if (status.fuel < 1.0) {
status.addFuel(resupplyRate);
}
// Repair hull
if (status.hull < 1.0) {
status.repairHull(resupplyRate);
}
// Replenish ammo
if (status.ammo < 1.0) {
status.addAmmo(resupplyRate);
}
}
} }
/** /**
@ -258,6 +315,22 @@ export class Ship {
return this._ship; return this._ship;
} }
/**
* Set the landing zone for resupply
*/
public setLandingZone(landingAggregate: PhysicsAggregate): void {
this._landingAggregate = landingAggregate;
// Listen for trigger events to detect when ship enters/exits landing zone
landingAggregate.body.getCollisionObservable().add((collisionEvent) => {
// Check if the collision is with our ship
if (collisionEvent.collider === this._ship.physicsBody) {
this._isInLandingZone = true;
debugLog("Ship entered landing zone - resupply active");
}
});
}
/** /**
* Add a VR controller to the input system * Add a VR controller to the input system
*/ */

View File

@ -11,13 +11,18 @@ import {GameConfig} from "./gameConfig";
import debugLog from "./debug"; import debugLog from "./debug";
import loadAsset from "./utils/loadAsset"; import loadAsset from "./utils/loadAsset";
export interface StarBaseResult {
baseMesh: AbstractMesh;
landingAggregate: PhysicsAggregate | null;
}
/** /**
* Create and load the star base mesh * Create and load the star base mesh
* @param position - Position for the star base * @param position - Position for the star base
* @returns Promise resolving to the loaded star base mesh * @returns Promise resolving to the loaded star base mesh and landing aggregate
*/ */
export default class StarBase { export default class StarBase {
public static async buildStarBase(): Promise<AbstractMesh> { public static async buildStarBase(): Promise<StarBaseResult> {
const config = GameConfig.getInstance(); const config = GameConfig.getInstance();
const scene = DefaultScene.MainScene; const scene = DefaultScene.MainScene;
const importMeshes = await loadAsset('base.glb'); const importMeshes = await loadAsset('base.glb');
@ -25,7 +30,7 @@ export default class StarBase {
const baseMesh = importMeshes.meshes.get('Base'); const baseMesh = importMeshes.meshes.get('Base');
const landingMesh = importMeshes.meshes.get('BaseLandingZone'); const landingMesh = importMeshes.meshes.get('BaseLandingZone');
let landingAgg: PhysicsAggregate | null = null;
if (config.physicsEnabled) { if (config.physicsEnabled) {
const agg2 = new PhysicsAggregate(baseMesh, PhysicsShapeType.MESH, { const agg2 = new PhysicsAggregate(baseMesh, PhysicsShapeType.MESH, {
@ -37,7 +42,7 @@ export default class StarBase {
debugLog('collidedBody', collidedBody); debugLog('collidedBody', collidedBody);
}) })
const landingAgg = new PhysicsAggregate(landingMesh, PhysicsShapeType.MESH); landingAgg = new PhysicsAggregate(landingMesh, PhysicsShapeType.MESH);
landingAgg.body.setMotionType(PhysicsMotionType.ANIMATED); landingAgg.body.setMotionType(PhysicsMotionType.ANIMATED);
/*landingAgg.body.getCollisionObservable().add((collidedCollidedBody) => { /*landingAgg.body.getCollisionObservable().add((collidedCollidedBody) => {
@ -50,7 +55,10 @@ export default class StarBase {
landingAgg.body.setCollisionCallbackEnabled(true); landingAgg.body.setCollisionCallbackEnabled(true);
} }
//importMesh.rootNodes[0].dispose(); //importMesh.rootNodes[0].dispose();
return baseMesh; return {
baseMesh,
landingAggregate: landingAgg
};
} }
} }
function clearParent (meshes: Map<string, AbstractMesh>, position?: Vector3) { function clearParent (meshes: Map<string, AbstractMesh>, position?: Vector3) {