From 827dd2d3599ac59f18ed7674e5e6352799455d8d Mon Sep 17 00:00:00 2001 From: Michael Mainguy Date: Fri, 7 Nov 2025 15:39:40 -0600 Subject: [PATCH] Add resupply system for base landing zone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/level1.ts | 9 +++++ src/levelDeserializer.ts | 11 +++--- src/ship.ts | 73 ++++++++++++++++++++++++++++++++++++++++ src/starBase.ts | 18 +++++++--- 4 files changed, 102 insertions(+), 9 deletions(-) diff --git a/src/level1.ts b/src/level1.ts index 0b52817..d2c6640 100644 --- a/src/level1.ts +++ b/src/level1.ts @@ -3,6 +3,7 @@ import type {AudioEngineV2} from "@babylonjs/core"; import { AbstractMesh, Observable, + PhysicsAggregate, Vector3 } from "@babylonjs/core"; import {Ship} from "./ship"; @@ -18,6 +19,7 @@ export class Level1 implements Level { private _onReadyObservable: Observable = new Observable(); private _initialized: boolean = false; private _startBase: AbstractMesh | null; + private _landingAggregate: PhysicsAggregate | null; private _endBase: AbstractMesh; private _levelConfig: LevelConfig; private _audioEngine: AudioEngineV2; @@ -103,6 +105,13 @@ export class Level1 implements Level { const entities = await this._deserializer.deserialize(this._ship.scoreboard.onScoreObservable); 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 // Initialize scoreboard with total asteroid count diff --git a/src/levelDeserializer.ts b/src/levelDeserializer.ts index 8cf66da..8b948e6 100644 --- a/src/levelDeserializer.ts +++ b/src/levelDeserializer.ts @@ -3,6 +3,7 @@ import { MeshBuilder, Observable, PBRMaterial, + PhysicsAggregate, Texture, Vector3 } from "@babylonjs/core"; @@ -43,6 +44,7 @@ export class LevelDeserializer { */ public async deserialize(scoreObservable: Observable): Promise<{ startBase: AbstractMesh | null; + landingAggregate: PhysicsAggregate | null; sun: AbstractMesh; planets: AbstractMesh[]; asteroids: AbstractMesh[]; @@ -50,7 +52,7 @@ export class LevelDeserializer { debugLog('Deserializing level:', this.config.difficulty); // Create entities - const startBase = await this.createStartBase(); + const baseResult = await this.createStartBase(); const sun = this.createSun(); const planets = this.createPlanets(); const asteroids = await this.createAsteroids(scoreObservable); @@ -63,7 +65,8 @@ export class LevelDeserializer { light2.intensity = .5; */ return { - startBase, + startBase: baseResult.baseMesh, + landingAggregate: baseResult.landingAggregate, sun, planets, asteroids @@ -73,8 +76,8 @@ export class LevelDeserializer { /** * Create the start base from config */ - private async createStartBase(): Promise { - return await StarBase.buildStarBase(); + private async createStartBase() { + return await StarBase.buildStarBase(); } /** diff --git a/src/ship.ts b/src/ship.ts index df5043b..9463e06 100644 --- a/src/ship.ts +++ b/src/ship.ts @@ -42,6 +42,11 @@ export class Ship { // Frame counter for physics updates private _frameCount: number = 0; + // Resupply system + private _landingAggregate: PhysicsAggregate | null = null; + private _resupplyTimer: number = 0; + private _isInLandingZone: boolean = false; + constructor(audioEngine?: AudioEngineV2) { this._audioEngine = audioEngine; } @@ -233,6 +238,58 @@ export class Ship { 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; } + /** + * 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 */ diff --git a/src/starBase.ts b/src/starBase.ts index dc4d152..4ec5850 100644 --- a/src/starBase.ts +++ b/src/starBase.ts @@ -11,13 +11,18 @@ import {GameConfig} from "./gameConfig"; import debugLog from "./debug"; import loadAsset from "./utils/loadAsset"; +export interface StarBaseResult { + baseMesh: AbstractMesh; + landingAggregate: PhysicsAggregate | null; +} + /** * Create and load the star base mesh * @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 { - public static async buildStarBase(): Promise { + public static async buildStarBase(): Promise { const config = GameConfig.getInstance(); const scene = DefaultScene.MainScene; const importMeshes = await loadAsset('base.glb'); @@ -25,7 +30,7 @@ export default class StarBase { const baseMesh = importMeshes.meshes.get('Base'); const landingMesh = importMeshes.meshes.get('BaseLandingZone'); - + let landingAgg: PhysicsAggregate | null = null; if (config.physicsEnabled) { const agg2 = new PhysicsAggregate(baseMesh, PhysicsShapeType.MESH, { @@ -37,7 +42,7 @@ export default class StarBase { debugLog('collidedBody', collidedBody); }) - const landingAgg = new PhysicsAggregate(landingMesh, PhysicsShapeType.MESH); + landingAgg = new PhysicsAggregate(landingMesh, PhysicsShapeType.MESH); landingAgg.body.setMotionType(PhysicsMotionType.ANIMATED); /*landingAgg.body.getCollisionObservable().add((collidedCollidedBody) => { @@ -50,7 +55,10 @@ export default class StarBase { landingAgg.body.setCollisionCallbackEnabled(true); } //importMesh.rootNodes[0].dispose(); - return baseMesh; + return { + baseMesh, + landingAggregate: landingAgg + }; } } function clearParent (meshes: Map, position?: Vector3) {