From ccc1745ed23f7fa7dd887987e23314c81102a530 Mon Sep 17 00:00:00 2001 From: Michael Mainguy Date: Mon, 10 Nov 2025 12:19:31 -0600 Subject: [PATCH] Refactor asteroid scaling and reorganize assets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major changes: - Change asteroid config to use single scale number instead of Vector3 - Move planetTextures to public/assets/materials/planetTextures - Add GLB path configuration for start base - Fix inspector toggle to work bidirectionally - Add progression system support Asteroid Scaling Changes: - Update AsteroidConfig interface to use 'scale: number' instead of 'scaling: Vector3Array' - Modify RockFactory.createRock() to accept single scale parameter - Update level serializer/deserializer to use uniform scale - Simplify level generation code in levelEditor and levelGenerator - Update validation to check for positive number instead of 3-element array Asset Organization: - Move public/planetTextures → public/assets/materials/planetTextures - Update all texture path references in planetTextures.ts (210 paths) - Update default texture paths in createSun.ts and levelSerializer.ts - Update CLAUDE.md documentation with new asset structure Start Base Improvements: - Add baseGlbPath and landingGlbPath to StartBaseConfig - Update StarBase.buildStarBase() to accept GLB path parameter - Add position parameter support to StarBase - Store GLB path in mesh metadata for serialization - Add UI field in level editor for base GLB path Inspector Toggle: - Fix 'i' key to toggle inspector on/off instead of only on - Use scene.debugLayer.isVisible() for state checking - Consistent with ReplayManager implementation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CLAUDE.md | 7 +- index.html | 45 ++- package.json | 2 +- .../planetTextures/Arid/Arid_01-512x512.png | Bin .../planetTextures/Arid/Arid_02-512x512.png | Bin .../planetTextures/Arid/Arid_03-512x512.png | Bin .../planetTextures/Arid/Arid_04-512x512.png | Bin .../planetTextures/Arid/Arid_05-512x512.png | Bin .../Barren/Barren_01-512x512.png | Bin .../Barren/Barren_02-512x512.png | Bin .../Barren/Barren_03-512x512.png | Bin .../Barren/Barren_04-512x512.png | Bin .../Barren/Barren_05-512x512.png | Bin .../planetTextures/Dusty/Dusty_01-512x512.png | Bin .../planetTextures/Dusty/Dusty_02-512x512.png | Bin .../planetTextures/Dusty/Dusty_03-512x512.png | Bin .../planetTextures/Dusty/Dusty_04-512x512.png | Bin .../planetTextures/Dusty/Dusty_05-512x512.png | Bin .../Gaseous/Gaseous_01-512x512.png | Bin .../Gaseous/Gaseous_02-512x512.png | Bin .../Gaseous/Gaseous_03-512x512.png | Bin .../Gaseous/Gaseous_04-512x512.png | Bin .../Gaseous/Gaseous_05-512x512.png | Bin .../Gaseous/Gaseous_06-512x512.png | Bin .../Gaseous/Gaseous_07-512x512.png | Bin .../Gaseous/Gaseous_08-512x512.png | Bin .../Gaseous/Gaseous_09-512x512.png | Bin .../Gaseous/Gaseous_10-512x512.png | Bin .../Gaseous/Gaseous_11-512x512.png | Bin .../Gaseous/Gaseous_12-512x512.png | Bin .../Gaseous/Gaseous_13-512x512.png | Bin .../Gaseous/Gaseous_14-512x512.png | Bin .../Gaseous/Gaseous_15-512x512.png | Bin .../Gaseous/Gaseous_16-512x512.png | Bin .../Gaseous/Gaseous_17-512x512.png | Bin .../Gaseous/Gaseous_18-512x512.png | Bin .../Gaseous/Gaseous_19-512x512.png | Bin .../Gaseous/Gaseous_20-512x512.png | Bin .../Grassland/Grassland_01-512x512.png | Bin .../Grassland/Grassland_02-512x512.png | Bin .../Grassland/Grassland_03-512x512.png | Bin .../Grassland/Grassland_04-512x512.png | Bin .../Grassland/Grassland_05-512x512.png | Bin .../Jungle/Jungle_01-512x512.png | Bin .../Jungle/Jungle_02-512x512.png | Bin .../Jungle/Jungle_03-512x512.png | Bin .../Jungle/Jungle_04-512x512.png | Bin .../Jungle/Jungle_05-512x512.png | Bin .../Marshy/Marshy_01-512x512.png | Bin .../Marshy/Marshy_02-512x512.png | Bin .../Marshy/Marshy_03-512x512.png | Bin .../Marshy/Marshy_04-512x512.png | Bin .../Marshy/Marshy_05-512x512.png | Bin .../Martian/Martian_01-512x512.png | Bin .../Martian/Martian_02-512x512.png | Bin .../Martian/Martian_03-512x512.png | Bin .../Martian/Martian_04-512x512.png | Bin .../Martian/Martian_05-512x512.png | Bin .../Methane/Methane_01-512x512.png | Bin .../Methane/Methane_02-512x512.png | Bin .../Methane/Methane_03-512x512.png | Bin .../Methane/Methane_04-512x512.png | Bin .../Methane/Methane_05-512x512.png | Bin .../planetTextures/Sandy/Sandy_01-512x512.png | Bin .../planetTextures/Sandy/Sandy_02-512x512.png | Bin .../planetTextures/Sandy/Sandy_03-512x512.png | Bin .../planetTextures/Sandy/Sandy_04-512x512.png | Bin .../planetTextures/Sandy/Sandy_05-512x512.png | Bin .../planetTextures/Snowy/Snowy_01-512x512.png | Bin .../planetTextures/Snowy/Snowy_02-512x512.png | Bin .../planetTextures/Snowy/Snowy_03-512x512.png | Bin .../planetTextures/Snowy/Snowy_04-512x512.png | Bin .../planetTextures/Snowy/Snowy_05-512x512.png | Bin .../Tundra/Tundra_01-512x512.png | Bin .../Tundra/Tundra_02-512x512.png | Bin .../Tundra/Tundra_03-512x512.png | Bin .../Tundra/Tundra_04-512x512.png | Bin .../Tundral-EQUIRECTANGULAR-5-512x512.png | Bin src/createSun.ts | 2 +- src/gameConfig.ts | 6 + src/keyboardInput.ts | 10 +- src/levelConfig.ts | 30 +- src/levelDeserializer.ts | 14 +- src/levelEditor.ts | 179 ++++++++++- src/levelGenerator.ts | 7 +- src/levelSelector.ts | 300 ++++++++++++++++-- src/levelSerializer.ts | 30 +- src/main.ts | 16 +- src/planetTextures.ts | 300 +++++++++--------- src/progression.ts | 266 ++++++++++++++++ src/rockFactory.ts | 4 +- src/ship.ts | 19 +- src/starBase.ts | 19 +- src/statusScreen.ts | 70 +++- 94 files changed, 1058 insertions(+), 268 deletions(-) rename public/{ => assets/materials}/planetTextures/Arid/Arid_01-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Arid/Arid_02-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Arid/Arid_03-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Arid/Arid_04-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Arid/Arid_05-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Barren/Barren_01-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Barren/Barren_02-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Barren/Barren_03-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Barren/Barren_04-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Barren/Barren_05-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Dusty/Dusty_01-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Dusty/Dusty_02-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Dusty/Dusty_03-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Dusty/Dusty_04-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Dusty/Dusty_05-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Gaseous/Gaseous_01-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Gaseous/Gaseous_02-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Gaseous/Gaseous_03-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Gaseous/Gaseous_04-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Gaseous/Gaseous_05-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Gaseous/Gaseous_06-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Gaseous/Gaseous_07-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Gaseous/Gaseous_08-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Gaseous/Gaseous_09-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Gaseous/Gaseous_10-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Gaseous/Gaseous_11-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Gaseous/Gaseous_12-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Gaseous/Gaseous_13-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Gaseous/Gaseous_14-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Gaseous/Gaseous_15-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Gaseous/Gaseous_16-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Gaseous/Gaseous_17-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Gaseous/Gaseous_18-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Gaseous/Gaseous_19-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Gaseous/Gaseous_20-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Grassland/Grassland_01-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Grassland/Grassland_02-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Grassland/Grassland_03-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Grassland/Grassland_04-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Grassland/Grassland_05-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Jungle/Jungle_01-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Jungle/Jungle_02-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Jungle/Jungle_03-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Jungle/Jungle_04-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Jungle/Jungle_05-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Marshy/Marshy_01-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Marshy/Marshy_02-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Marshy/Marshy_03-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Marshy/Marshy_04-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Marshy/Marshy_05-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Martian/Martian_01-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Martian/Martian_02-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Martian/Martian_03-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Martian/Martian_04-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Martian/Martian_05-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Methane/Methane_01-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Methane/Methane_02-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Methane/Methane_03-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Methane/Methane_04-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Methane/Methane_05-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Sandy/Sandy_01-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Sandy/Sandy_02-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Sandy/Sandy_03-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Sandy/Sandy_04-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Sandy/Sandy_05-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Snowy/Snowy_01-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Snowy/Snowy_02-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Snowy/Snowy_03-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Snowy/Snowy_04-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Snowy/Snowy_05-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Tundra/Tundra_01-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Tundra/Tundra_02-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Tundra/Tundra_03-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Tundra/Tundra_04-512x512.png (100%) rename public/{ => assets/materials}/planetTextures/Tundra/Tundral-EQUIRECTANGULAR-5-512x512.png (100%) create mode 100644 src/progression.ts diff --git a/CLAUDE.md b/CLAUDE.md index 1bead15..9370101 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -101,7 +101,7 @@ Located in `level1.ts:getDifficultyConfig()` ### Asset Loading - 3D models: GLB format (cockpit, asteroids) - Particle systems: JSON format in `public/systems/` -- Planet textures: Organized by biome in `public/planetTextures/` +- Planet textures: Organized by biome in `public/assets/materials/planetTextures/` - Audio: MP3 format in public root ### Performance Considerations @@ -128,7 +128,10 @@ src/ public/ systems/ - Particle system definitions - planetTextures/ - Biome-based planet textures + assets/ + materials/ + planetTextures/ - Biome-based planet textures + themes/ - Themed assets cockpit*.glb - Ship interior models asteroid*.glb - Asteroid mesh variants *.mp3 - Audio assets diff --git a/index.html b/index.html index 8620535..b4f0158 100644 --- a/index.html +++ b/index.html @@ -12,8 +12,6 @@ } }); - - @@ -26,9 +24,32 @@
- -
-

🎮 How to Play

+ +
+

+ 🚀 Space Combat VR +

+

+ Pilot your spaceship through asteroid fields and complete missions +

+
+ + +
+

Your Mission

+

+ Complete levels to unlock new challenges and the level editor +

+
+ +
+
+ + +
+ + 🎮 How to Play (Click to expand) +

VR Controllers (Required for VR)

@@ -52,11 +73,7 @@

⚠️ Note: This game is designed for VR headsets with controllers. Desktop controls are provided for preview and testing purposes only.

-
-

Select Your Level

-
- -
+
- - -
-
- - + +
diff --git a/package.json b/package.json index 75b3c6b..729240f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "space-game", - "deployHostname": "space.digital-experiment.com", + "deployHostname": "www.flatearthdefense.com", "private": false, "version": "0.0.1", "type": "module", diff --git a/public/planetTextures/Arid/Arid_01-512x512.png b/public/assets/materials/planetTextures/Arid/Arid_01-512x512.png similarity index 100% rename from public/planetTextures/Arid/Arid_01-512x512.png rename to public/assets/materials/planetTextures/Arid/Arid_01-512x512.png diff --git a/public/planetTextures/Arid/Arid_02-512x512.png b/public/assets/materials/planetTextures/Arid/Arid_02-512x512.png similarity index 100% rename from public/planetTextures/Arid/Arid_02-512x512.png rename to public/assets/materials/planetTextures/Arid/Arid_02-512x512.png diff --git a/public/planetTextures/Arid/Arid_03-512x512.png b/public/assets/materials/planetTextures/Arid/Arid_03-512x512.png similarity index 100% rename from public/planetTextures/Arid/Arid_03-512x512.png rename to public/assets/materials/planetTextures/Arid/Arid_03-512x512.png diff --git a/public/planetTextures/Arid/Arid_04-512x512.png b/public/assets/materials/planetTextures/Arid/Arid_04-512x512.png similarity index 100% rename from public/planetTextures/Arid/Arid_04-512x512.png rename to public/assets/materials/planetTextures/Arid/Arid_04-512x512.png diff --git a/public/planetTextures/Arid/Arid_05-512x512.png b/public/assets/materials/planetTextures/Arid/Arid_05-512x512.png similarity index 100% rename from public/planetTextures/Arid/Arid_05-512x512.png rename to public/assets/materials/planetTextures/Arid/Arid_05-512x512.png diff --git a/public/planetTextures/Barren/Barren_01-512x512.png b/public/assets/materials/planetTextures/Barren/Barren_01-512x512.png similarity index 100% rename from public/planetTextures/Barren/Barren_01-512x512.png rename to public/assets/materials/planetTextures/Barren/Barren_01-512x512.png diff --git a/public/planetTextures/Barren/Barren_02-512x512.png b/public/assets/materials/planetTextures/Barren/Barren_02-512x512.png similarity index 100% rename from public/planetTextures/Barren/Barren_02-512x512.png rename to public/assets/materials/planetTextures/Barren/Barren_02-512x512.png diff --git a/public/planetTextures/Barren/Barren_03-512x512.png b/public/assets/materials/planetTextures/Barren/Barren_03-512x512.png similarity index 100% rename from public/planetTextures/Barren/Barren_03-512x512.png rename to public/assets/materials/planetTextures/Barren/Barren_03-512x512.png diff --git a/public/planetTextures/Barren/Barren_04-512x512.png b/public/assets/materials/planetTextures/Barren/Barren_04-512x512.png similarity index 100% rename from public/planetTextures/Barren/Barren_04-512x512.png rename to public/assets/materials/planetTextures/Barren/Barren_04-512x512.png diff --git a/public/planetTextures/Barren/Barren_05-512x512.png b/public/assets/materials/planetTextures/Barren/Barren_05-512x512.png similarity index 100% rename from public/planetTextures/Barren/Barren_05-512x512.png rename to public/assets/materials/planetTextures/Barren/Barren_05-512x512.png diff --git a/public/planetTextures/Dusty/Dusty_01-512x512.png b/public/assets/materials/planetTextures/Dusty/Dusty_01-512x512.png similarity index 100% rename from public/planetTextures/Dusty/Dusty_01-512x512.png rename to public/assets/materials/planetTextures/Dusty/Dusty_01-512x512.png diff --git a/public/planetTextures/Dusty/Dusty_02-512x512.png b/public/assets/materials/planetTextures/Dusty/Dusty_02-512x512.png similarity index 100% rename from public/planetTextures/Dusty/Dusty_02-512x512.png rename to public/assets/materials/planetTextures/Dusty/Dusty_02-512x512.png diff --git a/public/planetTextures/Dusty/Dusty_03-512x512.png b/public/assets/materials/planetTextures/Dusty/Dusty_03-512x512.png similarity index 100% rename from public/planetTextures/Dusty/Dusty_03-512x512.png rename to public/assets/materials/planetTextures/Dusty/Dusty_03-512x512.png diff --git a/public/planetTextures/Dusty/Dusty_04-512x512.png b/public/assets/materials/planetTextures/Dusty/Dusty_04-512x512.png similarity index 100% rename from public/planetTextures/Dusty/Dusty_04-512x512.png rename to public/assets/materials/planetTextures/Dusty/Dusty_04-512x512.png diff --git a/public/planetTextures/Dusty/Dusty_05-512x512.png b/public/assets/materials/planetTextures/Dusty/Dusty_05-512x512.png similarity index 100% rename from public/planetTextures/Dusty/Dusty_05-512x512.png rename to public/assets/materials/planetTextures/Dusty/Dusty_05-512x512.png diff --git a/public/planetTextures/Gaseous/Gaseous_01-512x512.png b/public/assets/materials/planetTextures/Gaseous/Gaseous_01-512x512.png similarity index 100% rename from public/planetTextures/Gaseous/Gaseous_01-512x512.png rename to public/assets/materials/planetTextures/Gaseous/Gaseous_01-512x512.png diff --git a/public/planetTextures/Gaseous/Gaseous_02-512x512.png b/public/assets/materials/planetTextures/Gaseous/Gaseous_02-512x512.png similarity index 100% rename from public/planetTextures/Gaseous/Gaseous_02-512x512.png rename to public/assets/materials/planetTextures/Gaseous/Gaseous_02-512x512.png diff --git a/public/planetTextures/Gaseous/Gaseous_03-512x512.png b/public/assets/materials/planetTextures/Gaseous/Gaseous_03-512x512.png similarity index 100% rename from public/planetTextures/Gaseous/Gaseous_03-512x512.png rename to public/assets/materials/planetTextures/Gaseous/Gaseous_03-512x512.png diff --git a/public/planetTextures/Gaseous/Gaseous_04-512x512.png b/public/assets/materials/planetTextures/Gaseous/Gaseous_04-512x512.png similarity index 100% rename from public/planetTextures/Gaseous/Gaseous_04-512x512.png rename to public/assets/materials/planetTextures/Gaseous/Gaseous_04-512x512.png diff --git a/public/planetTextures/Gaseous/Gaseous_05-512x512.png b/public/assets/materials/planetTextures/Gaseous/Gaseous_05-512x512.png similarity index 100% rename from public/planetTextures/Gaseous/Gaseous_05-512x512.png rename to public/assets/materials/planetTextures/Gaseous/Gaseous_05-512x512.png diff --git a/public/planetTextures/Gaseous/Gaseous_06-512x512.png b/public/assets/materials/planetTextures/Gaseous/Gaseous_06-512x512.png similarity index 100% rename from public/planetTextures/Gaseous/Gaseous_06-512x512.png rename to public/assets/materials/planetTextures/Gaseous/Gaseous_06-512x512.png diff --git a/public/planetTextures/Gaseous/Gaseous_07-512x512.png b/public/assets/materials/planetTextures/Gaseous/Gaseous_07-512x512.png similarity index 100% rename from public/planetTextures/Gaseous/Gaseous_07-512x512.png rename to public/assets/materials/planetTextures/Gaseous/Gaseous_07-512x512.png diff --git a/public/planetTextures/Gaseous/Gaseous_08-512x512.png b/public/assets/materials/planetTextures/Gaseous/Gaseous_08-512x512.png similarity index 100% rename from public/planetTextures/Gaseous/Gaseous_08-512x512.png rename to public/assets/materials/planetTextures/Gaseous/Gaseous_08-512x512.png diff --git a/public/planetTextures/Gaseous/Gaseous_09-512x512.png b/public/assets/materials/planetTextures/Gaseous/Gaseous_09-512x512.png similarity index 100% rename from public/planetTextures/Gaseous/Gaseous_09-512x512.png rename to public/assets/materials/planetTextures/Gaseous/Gaseous_09-512x512.png diff --git a/public/planetTextures/Gaseous/Gaseous_10-512x512.png b/public/assets/materials/planetTextures/Gaseous/Gaseous_10-512x512.png similarity index 100% rename from public/planetTextures/Gaseous/Gaseous_10-512x512.png rename to public/assets/materials/planetTextures/Gaseous/Gaseous_10-512x512.png diff --git a/public/planetTextures/Gaseous/Gaseous_11-512x512.png b/public/assets/materials/planetTextures/Gaseous/Gaseous_11-512x512.png similarity index 100% rename from public/planetTextures/Gaseous/Gaseous_11-512x512.png rename to public/assets/materials/planetTextures/Gaseous/Gaseous_11-512x512.png diff --git a/public/planetTextures/Gaseous/Gaseous_12-512x512.png b/public/assets/materials/planetTextures/Gaseous/Gaseous_12-512x512.png similarity index 100% rename from public/planetTextures/Gaseous/Gaseous_12-512x512.png rename to public/assets/materials/planetTextures/Gaseous/Gaseous_12-512x512.png diff --git a/public/planetTextures/Gaseous/Gaseous_13-512x512.png b/public/assets/materials/planetTextures/Gaseous/Gaseous_13-512x512.png similarity index 100% rename from public/planetTextures/Gaseous/Gaseous_13-512x512.png rename to public/assets/materials/planetTextures/Gaseous/Gaseous_13-512x512.png diff --git a/public/planetTextures/Gaseous/Gaseous_14-512x512.png b/public/assets/materials/planetTextures/Gaseous/Gaseous_14-512x512.png similarity index 100% rename from public/planetTextures/Gaseous/Gaseous_14-512x512.png rename to public/assets/materials/planetTextures/Gaseous/Gaseous_14-512x512.png diff --git a/public/planetTextures/Gaseous/Gaseous_15-512x512.png b/public/assets/materials/planetTextures/Gaseous/Gaseous_15-512x512.png similarity index 100% rename from public/planetTextures/Gaseous/Gaseous_15-512x512.png rename to public/assets/materials/planetTextures/Gaseous/Gaseous_15-512x512.png diff --git a/public/planetTextures/Gaseous/Gaseous_16-512x512.png b/public/assets/materials/planetTextures/Gaseous/Gaseous_16-512x512.png similarity index 100% rename from public/planetTextures/Gaseous/Gaseous_16-512x512.png rename to public/assets/materials/planetTextures/Gaseous/Gaseous_16-512x512.png diff --git a/public/planetTextures/Gaseous/Gaseous_17-512x512.png b/public/assets/materials/planetTextures/Gaseous/Gaseous_17-512x512.png similarity index 100% rename from public/planetTextures/Gaseous/Gaseous_17-512x512.png rename to public/assets/materials/planetTextures/Gaseous/Gaseous_17-512x512.png diff --git a/public/planetTextures/Gaseous/Gaseous_18-512x512.png b/public/assets/materials/planetTextures/Gaseous/Gaseous_18-512x512.png similarity index 100% rename from public/planetTextures/Gaseous/Gaseous_18-512x512.png rename to public/assets/materials/planetTextures/Gaseous/Gaseous_18-512x512.png diff --git a/public/planetTextures/Gaseous/Gaseous_19-512x512.png b/public/assets/materials/planetTextures/Gaseous/Gaseous_19-512x512.png similarity index 100% rename from public/planetTextures/Gaseous/Gaseous_19-512x512.png rename to public/assets/materials/planetTextures/Gaseous/Gaseous_19-512x512.png diff --git a/public/planetTextures/Gaseous/Gaseous_20-512x512.png b/public/assets/materials/planetTextures/Gaseous/Gaseous_20-512x512.png similarity index 100% rename from public/planetTextures/Gaseous/Gaseous_20-512x512.png rename to public/assets/materials/planetTextures/Gaseous/Gaseous_20-512x512.png diff --git a/public/planetTextures/Grassland/Grassland_01-512x512.png b/public/assets/materials/planetTextures/Grassland/Grassland_01-512x512.png similarity index 100% rename from public/planetTextures/Grassland/Grassland_01-512x512.png rename to public/assets/materials/planetTextures/Grassland/Grassland_01-512x512.png diff --git a/public/planetTextures/Grassland/Grassland_02-512x512.png b/public/assets/materials/planetTextures/Grassland/Grassland_02-512x512.png similarity index 100% rename from public/planetTextures/Grassland/Grassland_02-512x512.png rename to public/assets/materials/planetTextures/Grassland/Grassland_02-512x512.png diff --git a/public/planetTextures/Grassland/Grassland_03-512x512.png b/public/assets/materials/planetTextures/Grassland/Grassland_03-512x512.png similarity index 100% rename from public/planetTextures/Grassland/Grassland_03-512x512.png rename to public/assets/materials/planetTextures/Grassland/Grassland_03-512x512.png diff --git a/public/planetTextures/Grassland/Grassland_04-512x512.png b/public/assets/materials/planetTextures/Grassland/Grassland_04-512x512.png similarity index 100% rename from public/planetTextures/Grassland/Grassland_04-512x512.png rename to public/assets/materials/planetTextures/Grassland/Grassland_04-512x512.png diff --git a/public/planetTextures/Grassland/Grassland_05-512x512.png b/public/assets/materials/planetTextures/Grassland/Grassland_05-512x512.png similarity index 100% rename from public/planetTextures/Grassland/Grassland_05-512x512.png rename to public/assets/materials/planetTextures/Grassland/Grassland_05-512x512.png diff --git a/public/planetTextures/Jungle/Jungle_01-512x512.png b/public/assets/materials/planetTextures/Jungle/Jungle_01-512x512.png similarity index 100% rename from public/planetTextures/Jungle/Jungle_01-512x512.png rename to public/assets/materials/planetTextures/Jungle/Jungle_01-512x512.png diff --git a/public/planetTextures/Jungle/Jungle_02-512x512.png b/public/assets/materials/planetTextures/Jungle/Jungle_02-512x512.png similarity index 100% rename from public/planetTextures/Jungle/Jungle_02-512x512.png rename to public/assets/materials/planetTextures/Jungle/Jungle_02-512x512.png diff --git a/public/planetTextures/Jungle/Jungle_03-512x512.png b/public/assets/materials/planetTextures/Jungle/Jungle_03-512x512.png similarity index 100% rename from public/planetTextures/Jungle/Jungle_03-512x512.png rename to public/assets/materials/planetTextures/Jungle/Jungle_03-512x512.png diff --git a/public/planetTextures/Jungle/Jungle_04-512x512.png b/public/assets/materials/planetTextures/Jungle/Jungle_04-512x512.png similarity index 100% rename from public/planetTextures/Jungle/Jungle_04-512x512.png rename to public/assets/materials/planetTextures/Jungle/Jungle_04-512x512.png diff --git a/public/planetTextures/Jungle/Jungle_05-512x512.png b/public/assets/materials/planetTextures/Jungle/Jungle_05-512x512.png similarity index 100% rename from public/planetTextures/Jungle/Jungle_05-512x512.png rename to public/assets/materials/planetTextures/Jungle/Jungle_05-512x512.png diff --git a/public/planetTextures/Marshy/Marshy_01-512x512.png b/public/assets/materials/planetTextures/Marshy/Marshy_01-512x512.png similarity index 100% rename from public/planetTextures/Marshy/Marshy_01-512x512.png rename to public/assets/materials/planetTextures/Marshy/Marshy_01-512x512.png diff --git a/public/planetTextures/Marshy/Marshy_02-512x512.png b/public/assets/materials/planetTextures/Marshy/Marshy_02-512x512.png similarity index 100% rename from public/planetTextures/Marshy/Marshy_02-512x512.png rename to public/assets/materials/planetTextures/Marshy/Marshy_02-512x512.png diff --git a/public/planetTextures/Marshy/Marshy_03-512x512.png b/public/assets/materials/planetTextures/Marshy/Marshy_03-512x512.png similarity index 100% rename from public/planetTextures/Marshy/Marshy_03-512x512.png rename to public/assets/materials/planetTextures/Marshy/Marshy_03-512x512.png diff --git a/public/planetTextures/Marshy/Marshy_04-512x512.png b/public/assets/materials/planetTextures/Marshy/Marshy_04-512x512.png similarity index 100% rename from public/planetTextures/Marshy/Marshy_04-512x512.png rename to public/assets/materials/planetTextures/Marshy/Marshy_04-512x512.png diff --git a/public/planetTextures/Marshy/Marshy_05-512x512.png b/public/assets/materials/planetTextures/Marshy/Marshy_05-512x512.png similarity index 100% rename from public/planetTextures/Marshy/Marshy_05-512x512.png rename to public/assets/materials/planetTextures/Marshy/Marshy_05-512x512.png diff --git a/public/planetTextures/Martian/Martian_01-512x512.png b/public/assets/materials/planetTextures/Martian/Martian_01-512x512.png similarity index 100% rename from public/planetTextures/Martian/Martian_01-512x512.png rename to public/assets/materials/planetTextures/Martian/Martian_01-512x512.png diff --git a/public/planetTextures/Martian/Martian_02-512x512.png b/public/assets/materials/planetTextures/Martian/Martian_02-512x512.png similarity index 100% rename from public/planetTextures/Martian/Martian_02-512x512.png rename to public/assets/materials/planetTextures/Martian/Martian_02-512x512.png diff --git a/public/planetTextures/Martian/Martian_03-512x512.png b/public/assets/materials/planetTextures/Martian/Martian_03-512x512.png similarity index 100% rename from public/planetTextures/Martian/Martian_03-512x512.png rename to public/assets/materials/planetTextures/Martian/Martian_03-512x512.png diff --git a/public/planetTextures/Martian/Martian_04-512x512.png b/public/assets/materials/planetTextures/Martian/Martian_04-512x512.png similarity index 100% rename from public/planetTextures/Martian/Martian_04-512x512.png rename to public/assets/materials/planetTextures/Martian/Martian_04-512x512.png diff --git a/public/planetTextures/Martian/Martian_05-512x512.png b/public/assets/materials/planetTextures/Martian/Martian_05-512x512.png similarity index 100% rename from public/planetTextures/Martian/Martian_05-512x512.png rename to public/assets/materials/planetTextures/Martian/Martian_05-512x512.png diff --git a/public/planetTextures/Methane/Methane_01-512x512.png b/public/assets/materials/planetTextures/Methane/Methane_01-512x512.png similarity index 100% rename from public/planetTextures/Methane/Methane_01-512x512.png rename to public/assets/materials/planetTextures/Methane/Methane_01-512x512.png diff --git a/public/planetTextures/Methane/Methane_02-512x512.png b/public/assets/materials/planetTextures/Methane/Methane_02-512x512.png similarity index 100% rename from public/planetTextures/Methane/Methane_02-512x512.png rename to public/assets/materials/planetTextures/Methane/Methane_02-512x512.png diff --git a/public/planetTextures/Methane/Methane_03-512x512.png b/public/assets/materials/planetTextures/Methane/Methane_03-512x512.png similarity index 100% rename from public/planetTextures/Methane/Methane_03-512x512.png rename to public/assets/materials/planetTextures/Methane/Methane_03-512x512.png diff --git a/public/planetTextures/Methane/Methane_04-512x512.png b/public/assets/materials/planetTextures/Methane/Methane_04-512x512.png similarity index 100% rename from public/planetTextures/Methane/Methane_04-512x512.png rename to public/assets/materials/planetTextures/Methane/Methane_04-512x512.png diff --git a/public/planetTextures/Methane/Methane_05-512x512.png b/public/assets/materials/planetTextures/Methane/Methane_05-512x512.png similarity index 100% rename from public/planetTextures/Methane/Methane_05-512x512.png rename to public/assets/materials/planetTextures/Methane/Methane_05-512x512.png diff --git a/public/planetTextures/Sandy/Sandy_01-512x512.png b/public/assets/materials/planetTextures/Sandy/Sandy_01-512x512.png similarity index 100% rename from public/planetTextures/Sandy/Sandy_01-512x512.png rename to public/assets/materials/planetTextures/Sandy/Sandy_01-512x512.png diff --git a/public/planetTextures/Sandy/Sandy_02-512x512.png b/public/assets/materials/planetTextures/Sandy/Sandy_02-512x512.png similarity index 100% rename from public/planetTextures/Sandy/Sandy_02-512x512.png rename to public/assets/materials/planetTextures/Sandy/Sandy_02-512x512.png diff --git a/public/planetTextures/Sandy/Sandy_03-512x512.png b/public/assets/materials/planetTextures/Sandy/Sandy_03-512x512.png similarity index 100% rename from public/planetTextures/Sandy/Sandy_03-512x512.png rename to public/assets/materials/planetTextures/Sandy/Sandy_03-512x512.png diff --git a/public/planetTextures/Sandy/Sandy_04-512x512.png b/public/assets/materials/planetTextures/Sandy/Sandy_04-512x512.png similarity index 100% rename from public/planetTextures/Sandy/Sandy_04-512x512.png rename to public/assets/materials/planetTextures/Sandy/Sandy_04-512x512.png diff --git a/public/planetTextures/Sandy/Sandy_05-512x512.png b/public/assets/materials/planetTextures/Sandy/Sandy_05-512x512.png similarity index 100% rename from public/planetTextures/Sandy/Sandy_05-512x512.png rename to public/assets/materials/planetTextures/Sandy/Sandy_05-512x512.png diff --git a/public/planetTextures/Snowy/Snowy_01-512x512.png b/public/assets/materials/planetTextures/Snowy/Snowy_01-512x512.png similarity index 100% rename from public/planetTextures/Snowy/Snowy_01-512x512.png rename to public/assets/materials/planetTextures/Snowy/Snowy_01-512x512.png diff --git a/public/planetTextures/Snowy/Snowy_02-512x512.png b/public/assets/materials/planetTextures/Snowy/Snowy_02-512x512.png similarity index 100% rename from public/planetTextures/Snowy/Snowy_02-512x512.png rename to public/assets/materials/planetTextures/Snowy/Snowy_02-512x512.png diff --git a/public/planetTextures/Snowy/Snowy_03-512x512.png b/public/assets/materials/planetTextures/Snowy/Snowy_03-512x512.png similarity index 100% rename from public/planetTextures/Snowy/Snowy_03-512x512.png rename to public/assets/materials/planetTextures/Snowy/Snowy_03-512x512.png diff --git a/public/planetTextures/Snowy/Snowy_04-512x512.png b/public/assets/materials/planetTextures/Snowy/Snowy_04-512x512.png similarity index 100% rename from public/planetTextures/Snowy/Snowy_04-512x512.png rename to public/assets/materials/planetTextures/Snowy/Snowy_04-512x512.png diff --git a/public/planetTextures/Snowy/Snowy_05-512x512.png b/public/assets/materials/planetTextures/Snowy/Snowy_05-512x512.png similarity index 100% rename from public/planetTextures/Snowy/Snowy_05-512x512.png rename to public/assets/materials/planetTextures/Snowy/Snowy_05-512x512.png diff --git a/public/planetTextures/Tundra/Tundra_01-512x512.png b/public/assets/materials/planetTextures/Tundra/Tundra_01-512x512.png similarity index 100% rename from public/planetTextures/Tundra/Tundra_01-512x512.png rename to public/assets/materials/planetTextures/Tundra/Tundra_01-512x512.png diff --git a/public/planetTextures/Tundra/Tundra_02-512x512.png b/public/assets/materials/planetTextures/Tundra/Tundra_02-512x512.png similarity index 100% rename from public/planetTextures/Tundra/Tundra_02-512x512.png rename to public/assets/materials/planetTextures/Tundra/Tundra_02-512x512.png diff --git a/public/planetTextures/Tundra/Tundra_03-512x512.png b/public/assets/materials/planetTextures/Tundra/Tundra_03-512x512.png similarity index 100% rename from public/planetTextures/Tundra/Tundra_03-512x512.png rename to public/assets/materials/planetTextures/Tundra/Tundra_03-512x512.png diff --git a/public/planetTextures/Tundra/Tundra_04-512x512.png b/public/assets/materials/planetTextures/Tundra/Tundra_04-512x512.png similarity index 100% rename from public/planetTextures/Tundra/Tundra_04-512x512.png rename to public/assets/materials/planetTextures/Tundra/Tundra_04-512x512.png diff --git a/public/planetTextures/Tundra/Tundral-EQUIRECTANGULAR-5-512x512.png b/public/assets/materials/planetTextures/Tundra/Tundral-EQUIRECTANGULAR-5-512x512.png similarity index 100% rename from public/planetTextures/Tundra/Tundral-EQUIRECTANGULAR-5-512x512.png rename to public/assets/materials/planetTextures/Tundra/Tundral-EQUIRECTANGULAR-5-512x512.png diff --git a/src/createSun.ts b/src/createSun.ts index 368fa1b..b0d5687 100644 --- a/src/createSun.ts +++ b/src/createSun.ts @@ -36,7 +36,7 @@ export function createSun() : AbstractMesh { export function createPlanet(position: Vector3, diameter: number, name: string) : AbstractMesh { const planet = MeshBuilder.CreateSphere(name, {diameter: diameter, segments: 32}, DefaultScene.MainScene); const material = new StandardMaterial(name + "-material", DefaultScene.MainScene); - const texture = new Texture("/planetTextures/Arid/Arid_01-512x512.png", DefaultScene.MainScene); + const texture = new Texture("/assets/materials/planetTextures/Arid/Arid_01-512x512.png", DefaultScene.MainScene); material.diffuseTexture = texture; material.ambientTexture = texture; material.roughness = 1; diff --git a/src/gameConfig.ts b/src/gameConfig.ts index 5bc252a..43c8264 100644 --- a/src/gameConfig.ts +++ b/src/gameConfig.ts @@ -9,6 +9,9 @@ export class GameConfig { // Physics settings public physicsEnabled: boolean = true; + // Feature flags + public progressionEnabled: boolean = false; // Set to false for simple rookie level + // Ship physics tuning parameters public shipPhysics = { maxLinearVelocity: 200, @@ -42,6 +45,7 @@ export class GameConfig { const config = { physicsEnabled: this.physicsEnabled, debug: this.debug, + progressionEnabled: this.progressionEnabled, shipPhysics: this.shipPhysics }; localStorage.setItem('game-config', JSON.stringify(config)); @@ -57,6 +61,7 @@ export class GameConfig { const config = JSON.parse(stored); this.physicsEnabled = config.physicsEnabled ?? true; this.debug = config.debug ?? false; + this.progressionEnabled = config.progressionEnabled ?? false; // Load ship physics with fallback to defaults if (config.shipPhysics) { @@ -81,6 +86,7 @@ export class GameConfig { public reset(): void { this.physicsEnabled = true; this.debug = false; + this.progressionEnabled = false; this.shipPhysics = { maxLinearVelocity: 200, maxAngularVelocity: 1.4, diff --git a/src/keyboardInput.ts b/src/keyboardInput.ts index 8e763b4..44beaaf 100644 --- a/src/keyboardInput.ts +++ b/src/keyboardInput.ts @@ -100,13 +100,15 @@ export class KeyboardInput { document.onkeydown = (ev) => { // Always allow inspector and camera toggle, even when disabled if (ev.key === 'i') { - // Open Babylon Inspector - import("@babylonjs/inspector").then((inspector) => { - inspector.Inspector.Show(this._scene, { + // Toggle Babylon Inspector + if (this._scene.debugLayer.isVisible()) { + this._scene.debugLayer.hide(); + } else { + this._scene.debugLayer.show({ overlay: true, showExplorer: true, }); - }); + } return; } diff --git a/src/levelConfig.ts b/src/levelConfig.ts index 222563e..19755f3 100644 --- a/src/levelConfig.ts +++ b/src/levelConfig.ts @@ -74,10 +74,9 @@ export interface ShipConfig { * All fields optional to allow levels without start bases */ export interface StartBaseConfig { - position?: Vector3Array; - diameter?: number; - height?: number; - color?: Vector3Array; // RGB values 0-1 + position?: Vector3Array; // Defaults to [0, 0, 0] if not specified + baseGlbPath?: string; // Path to base GLB model (defaults to 'base.glb') + landingGlbPath?: string; // Path to landing zone GLB model (uses same file as base, different mesh name) } /** @@ -106,7 +105,7 @@ export interface PlanetConfig { export interface AsteroidConfig { id: string; position: Vector3Array; - scaling: Vector3Array; + scale: number; // Uniform scale applied to all axes linearVelocity: Vector3Array; angularVelocity?: Vector3Array; mass?: number; @@ -192,12 +191,6 @@ export function validateLevelConfig(config: any): ValidationResult { if (config.startBase.position && (!Array.isArray(config.startBase.position) || config.startBase.position.length !== 3)) { errors.push('Invalid startBase.position - must be [x, y, z] array'); } - if (config.startBase.diameter !== undefined && typeof config.startBase.diameter !== 'number') { - errors.push('Invalid startBase.diameter - must be a number'); - } - if (config.startBase.height !== undefined && typeof config.startBase.height !== 'number') { - errors.push('Invalid startBase.height - must be a number'); - } } // Check sun @@ -236,6 +229,17 @@ export function validateLevelConfig(config: any): ValidationResult { if (!Array.isArray(config.asteroids)) { errors.push('Missing or invalid asteroids array'); } else { + // HYBRID MIGRATION NOTE: If we need to support legacy localStorage data, + // add migration here before validation: + // + // config.asteroids = config.asteroids.map((a, i) => ({ + // ...a, + // id: a.id || `asteroid-${i}`, // Auto-generate missing ids + // scale: a.scale || a.scaling?.[0] || a.size || 1 // Migrate from old formats + // })); + // + // This would auto-heal old data with "scaling" array or "size" property + config.asteroids.forEach((asteroid: any, idx: number) => { if (!asteroid.id || typeof asteroid.id !== 'string') { errors.push(`Asteroid ${idx}: missing or invalid id`); @@ -243,8 +247,8 @@ export function validateLevelConfig(config: any): ValidationResult { if (!Array.isArray(asteroid.position) || asteroid.position.length !== 3) { errors.push(`Asteroid ${idx}: invalid position - must be [x, y, z] array`); } - if (!Array.isArray(asteroid.scaling) || asteroid.scaling.length !== 3) { - errors.push(`Asteroid ${idx}: invalid scaling - must be [x, y, z] array`); + if (typeof asteroid.scale !== 'number' || asteroid.scale <= 0) { + errors.push(`Asteroid ${idx}: invalid scale - must be a positive number`); } if (!Array.isArray(asteroid.linearVelocity) || asteroid.linearVelocity.length !== 3) { errors.push(`Asteroid ${idx}: invalid linearVelocity - must be [x, y, z] array`); diff --git a/src/levelDeserializer.ts b/src/levelDeserializer.ts index d9eb627..ab9418b 100644 --- a/src/levelDeserializer.ts +++ b/src/levelDeserializer.ts @@ -30,6 +30,14 @@ export class LevelDeserializer { private config: LevelConfig; constructor(config: LevelConfig) { + // HYBRID MIGRATION NOTE: If validation fails due to legacy data, + // consider adding migration logic here before validation: + // + // config = migrateLegacyFormat(config); + // + // This would allow smooth transition for users with old localStorage data + // See levelConfig.ts validateLevelConfig() for example migration code + // Validate config first const validation = validateLevelConfig(config); if (!validation.valid) { @@ -72,7 +80,9 @@ export class LevelDeserializer { * Create the start base from config */ private async createStartBase() { - return await StarBase.buildStarBase(); + const position = this.config.startBase?.position; + const baseGlbPath = this.config.startBase?.baseGlbPath || 'base.glb'; + return await StarBase.buildStarBase(position, baseGlbPath); } /** @@ -166,7 +176,7 @@ export class LevelDeserializer { const rock = await RockFactory.createRock( i, this.arrayToVector3(asteroidConfig.position), - this.arrayToVector3(asteroidConfig.scaling), + asteroidConfig.scale, this.arrayToVector3(asteroidConfig.linearVelocity), this.arrayToVector3(asteroidConfig.angularVelocity), scoreObservable diff --git a/src/levelEditor.ts b/src/levelEditor.ts index 2c6162a..1742f8d 100644 --- a/src/levelEditor.ts +++ b/src/levelEditor.ts @@ -1,5 +1,5 @@ import { LevelGenerator } from "./levelGenerator"; -import { LevelConfig, DifficultyConfig, validateLevelConfig } from "./levelConfig"; +import { LevelConfig, DifficultyConfig, validateLevelConfig, Vector3Array } from "./levelConfig"; import debugLog from './debug'; const STORAGE_KEY = 'space-game-levels'; @@ -171,8 +171,7 @@ class LevelEditor { (document.getElementById('baseX') as HTMLInputElement).value = config.startBase.position[0].toString(); (document.getElementById('baseY') as HTMLInputElement).value = config.startBase.position[1].toString(); (document.getElementById('baseZ') as HTMLInputElement).value = config.startBase.position[2].toString(); - (document.getElementById('baseDiameter') as HTMLInputElement).value = config.startBase.diameter.toString(); - (document.getElementById('baseHeight') as HTMLInputElement).value = config.startBase.height.toString(); + (document.getElementById('baseGlbPath') as HTMLInputElement).value = config.startBase.baseGlbPath || 'base.glb'; // Sun (document.getElementById('sunX') as HTMLInputElement).value = config.sun.position[0].toString(); @@ -596,9 +595,111 @@ export function getSavedLevel(name: string): LevelConfig | null { return levels.get(name) || null; } +/** + * Generate a simple rookie level with 4 asteroids + * Asteroids at 100-200 distance with 20-100 tangential velocities + */ +function generateSimpleRookieLevel(): void { + debugLog('Creating simple rookie level with 4 asteroids...'); + + const levelsMap = new Map(); + + // Create base level structure + const config: LevelConfig = { + version: "1.0", + difficulty: "rookie", + timestamp: new Date().toISOString(), + metadata: { + author: 'System', + description: 'Simple rookie training mission with 4 asteroids', + type: 'default' + }, + ship: { + position: [0, 1, 0], + rotation: [0, 0, 0], + linearVelocity: [0, 0, 0], + angularVelocity: [0, 0, 0] + }, + startBase: { + position: [0, 0, 0], + baseGlbPath: 'base.glb' + }, + sun: { + position: [0, 0, 400], + diameter: 50, + intensity: 1000000 + }, + planets: [], + asteroids: [], + difficultyConfig: { + rockCount: 4, + forceMultiplier: 1.0, + rockSizeMin: 3, + rockSizeMax: 5, + distanceMin: 100, + distanceMax: 200 + } + }; + + // Generate 4 asteroids with tangential velocities + const basePosition = [0, 0, 0]; // Start base position + + for (let i = 0; i < 4; i++) { + // Random distance between 100-200 + const distance = 100 + Math.random() * 100; + + // Random angle around the base + const angle = (Math.PI * 2 / 4) * i + (Math.random() - 0.5) * 0.5; + + // Position at distance and angle + const x = basePosition[0] + distance * Math.cos(angle); + const z = basePosition[2] + distance * Math.sin(angle); + const y = basePosition[1] + (Math.random() - 0.5) * 20; // Some vertical variation + + // Calculate tangent direction (perpendicular to radial) + const tangentX = -Math.sin(angle); + const tangentZ = Math.cos(angle); + + // Random tangential speed between 20-100 + const speed = 20 + Math.random() * 80; + + const linearVelocity: Vector3Array = [ + tangentX * speed, + (Math.random() - 0.5) * 10, // Small vertical velocity + tangentZ * speed + ]; + + // Random size between min and max + const scale = 3 + Math.random() * 2; + + // Random rotation + const angularVelocity: Vector3Array = [ + (Math.random() - 0.5) * 2, + (Math.random() - 0.5) * 2, + (Math.random() - 0.5) * 2 + ]; + + config.asteroids.push({ + id: `asteroid-${i}`, + position: [x, y, z], + scale, + linearVelocity, + angularVelocity + }); + } + + levelsMap.set('Rookie Training', config); + debugLog('Generated simple rookie level with 4 asteroids'); + + // Save to localStorage + const levelsArray = Array.from(levelsMap.entries()); + localStorage.setItem(STORAGE_KEY, JSON.stringify(levelsArray)); + debugLog('Simple rookie level saved to localStorage'); +} + /** * Generate default levels if localStorage is empty - * Creates 4 levels: recruit, pilot, captain, commander + * Creates either a simple rookie level or 6 themed levels based on progression flag */ export function generateDefaultLevels(): void { const existing = getSavedLevels(); @@ -607,30 +708,82 @@ export function generateDefaultLevels(): void { return; } - debugLog('No saved levels found, generating 4 default levels...'); + // Check progression flag from GameConfig + const GameConfig = (window as any).GameConfig; + const progressionEnabled = GameConfig?.getInstance().progressionEnabled ?? false; + + if (!progressionEnabled) { + debugLog('Progression disabled - generating simple rookie level...'); + generateSimpleRookieLevel(); + return; + } + + debugLog('No saved levels found, generating 6 default levels...'); + + // Define themed default levels with descriptions + const defaultLevels = [ + { + name: 'Tutorial: Asteroid Field', + difficulty: 'recruit', + description: 'Learn the basics of ship control and asteroid destruction in a calm sector of space.', + estimatedTime: '3-5 minutes' + }, + { + name: 'Rescue Mission', + difficulty: 'pilot', + description: 'Clear a path through moderate asteroid density to reach the stranded station.', + estimatedTime: '5-8 minutes' + }, + { + name: 'Deep Space Patrol', + difficulty: 'captain', + description: 'Patrol a dangerous sector with heavy asteroid activity. Watch your fuel!', + estimatedTime: '8-12 minutes' + }, + { + name: 'Enemy Territory', + difficulty: 'commander', + description: 'Navigate through hostile space with high-speed asteroids and limited resources.', + estimatedTime: '12-15 minutes' + }, + { + name: 'The Gauntlet', + difficulty: 'commander', + description: 'Face maximum asteroid density in this ultimate test of piloting skill.', + estimatedTime: '15-20 minutes' + }, + { + name: 'Final Challenge', + difficulty: 'commander', + description: 'The ultimate challenge - survive the most chaotic asteroid field in known space.', + estimatedTime: '20+ minutes' + } + ]; - const difficulties = ['recruit', 'pilot', 'captain', 'commander']; const levelsMap = new Map(); - for (const difficulty of difficulties) { - const generator = new LevelGenerator(difficulty); + for (const level of defaultLevels) { + const generator = new LevelGenerator(level.difficulty); const config = generator.generate(); - // Add metadata + // Add rich metadata config.metadata = { author: 'System', - description: `Default ${difficulty} level` + description: level.description, + estimatedTime: level.estimatedTime, + type: 'default', + difficulty: level.difficulty }; - levelsMap.set(difficulty, config); - debugLog(`Generated default level: ${difficulty}`); + levelsMap.set(level.name, config); + debugLog(`Generated default level: ${level.name} (${level.difficulty})`); } // Save all levels to localStorage const levelsArray = Array.from(levelsMap.entries()); localStorage.setItem(STORAGE_KEY, JSON.stringify(levelsArray)); - debugLog('Default levels saved to localStorage'); + debugLog(`${defaultLevels.length} default levels saved to localStorage`); } // Export for manual initialization if needed diff --git a/src/levelGenerator.ts b/src/levelGenerator.ts index c81518a..8446583 100644 --- a/src/levelGenerator.ts +++ b/src/levelGenerator.ts @@ -159,10 +159,9 @@ export class LevelGenerator { const position: Vector3Array = [x, y, z]; - // Random size + // Random size (uniform scale) const sizeRange = config.rockSizeMax - config.rockSizeMin; - const size = Math.random() * sizeRange + config.rockSizeMin; - const scaling: Vector3Array = [size, size, size]; + const scale = Math.random() * sizeRange + config.rockSizeMin; // Calculate initial velocity based on force applied in Level1 // Velocity should be tangential to the sphere (perpendicular to radius) @@ -182,7 +181,7 @@ export class LevelGenerator { asteroids.push({ id: `asteroid-${i}`, position, - scaling, + scale, linearVelocity, angularVelocity: [0, 0, 0], mass diff --git a/src/levelSelector.ts b/src/levelSelector.ts index d87afa2..be73d7c 100644 --- a/src/levelSelector.ts +++ b/src/levelSelector.ts @@ -1,11 +1,14 @@ import { getSavedLevels } from "./levelEditor"; import { LevelConfig } from "./levelConfig"; +import { ProgressionManager } from "./progression"; +import { GameConfig } from "./gameConfig"; import debugLog from './debug'; const SELECTED_LEVEL_KEY = 'space-game-selected-level'; /** * Populate the level selection screen with saved levels + * Shows default levels and custom levels with progression tracking */ export function populateLevelSelector(): boolean { const container = document.getElementById('levelCardsContainer'); @@ -15,16 +18,11 @@ export function populateLevelSelector(): boolean { } const savedLevels = getSavedLevels(); + const gameConfig = GameConfig.getInstance(); + const progressionEnabled = gameConfig.progressionEnabled; + const progression = ProgressionManager.getInstance(); - // Filter to only show recruit and pilot difficulty levels - const filteredLevels = new Map(); - for (const [name, config] of savedLevels.entries()) { - if (config.difficulty === 'recruit' || config.difficulty === 'pilot') { - filteredLevels.set(name, config); - } - } - - if (filteredLevels.size === 0) { + if (savedLevels.size === 0) { container.innerHTML = `

No Levels Found

-

Create your first level to get started!

+

Something went wrong - default levels should be auto-generated!

-

${name}

-
- Difficulty: ${config.difficulty} +
+

Progress

+
+ ${completedCount} of ${totalCount} default levels completed (${completionPercent.toFixed(0)}%)
-

${description}

- ${timestamp ? `
${timestamp}
` : ''} - +
+
+
+ ${nextLevel ? `
Next: ${nextLevel}
` : ''}
`; } + // Default levels section - show all levels if progression disabled, or current/next if enabled + if (defaultLevels.size > 0) { + html += ` +
+

Available Levels

+
+ `; + + // If progression is disabled, just show all default levels + if (!progressionEnabled) { + for (const [name, config] of defaultLevels.entries()) { + const description = config.metadata?.description || `${config.asteroids.length} asteroids • ${config.planets.length} planets`; + const estimatedTime = config.metadata?.estimatedTime || ''; + + html += ` +
+

${name}

+
+ Difficulty: ${config.difficulty}${estimatedTime ? ` • ${estimatedTime}` : ''} +
+

${description}

+ +
+ `; + } + } else { + // Progression enabled - show current and next level only + // Get the default level names in order + const defaultLevelNames = [ + 'Tutorial: Asteroid Field', + 'Rescue Mission', + 'Deep Space Patrol', + 'Enemy Territory', + 'The Gauntlet', + 'Final Challenge' + ]; + + // Find current level (last completed or first if none completed) + let currentLevelName: string | null = null; + let nextLevelName: string | null = null; + + // Find the first incomplete level (this is the "next" level) + for (let i = 0; i < defaultLevelNames.length; i++) { + const levelName = defaultLevelNames[i]; + if (!progression.isLevelComplete(levelName)) { + nextLevelName = levelName; + // Current level is the one before (if it exists) + if (i > 0) { + currentLevelName = defaultLevelNames[i - 1]; + } + break; + } + } + + // If all levels complete, show the last level as current + if (!nextLevelName) { + currentLevelName = defaultLevelNames[defaultLevelNames.length - 1]; + } + + // If no levels completed yet, show first as next (no current) + if (!currentLevelName && nextLevelName) { + // First time player - just show the first level + const config = defaultLevels.get(nextLevelName); + if (config) { + const description = config.metadata?.description || `${config.asteroids.length} asteroids • ${config.planets.length} planets`; + const estimatedTime = config.metadata?.estimatedTime || ''; + + html += ` +
+
+

${nextLevelName}

+
START HERE
+
+
+ Difficulty: ${config.difficulty}${estimatedTime ? ` • ${estimatedTime}` : ''} +
+

${description}

+ +
+ `; + } + } else { + // Show current (completed) level + if (currentLevelName) { + const config = defaultLevels.get(currentLevelName); + if (config) { + const levelProgress = progression.getLevelProgress(currentLevelName); + const description = config.metadata?.description || `${config.asteroids.length} asteroids • ${config.planets.length} planets`; + const estimatedTime = config.metadata?.estimatedTime || ''; + + html += ` +
+
+

${currentLevelName}

+
+
+
+ Difficulty: ${config.difficulty}${estimatedTime ? ` • ${estimatedTime}` : ''} +
+

${description}

+ ${levelProgress?.playCount ? `
Played ${levelProgress.playCount} time${levelProgress.playCount > 1 ? 's' : ''}
` : ''} + +
+ `; + } + } + + // Show next level if it exists + if (nextLevelName) { + const config = defaultLevels.get(nextLevelName); + if (config) { + const description = config.metadata?.description || `${config.asteroids.length} asteroids • ${config.planets.length} planets`; + const estimatedTime = config.metadata?.estimatedTime || ''; + + html += ` +
+
+

${nextLevelName}

+
NEXT
+
+
+ Difficulty: ${config.difficulty}${estimatedTime ? ` • ${estimatedTime}` : ''} +
+

${description}

+ +
+ `; + } + } + } + + // Show "more levels beyond" indicator if there are additional levels after next + const nextLevelIndex = defaultLevelNames.indexOf(nextLevelName || ''); + const hasMoreLevels = nextLevelIndex >= 0 && nextLevelIndex < defaultLevelNames.length - 1; + + if (hasMoreLevels) { + const remainingCount = defaultLevelNames.length - nextLevelIndex - 1; + html += ` +
+
✦ ✦ ✦
+
+ ${remainingCount} more level${remainingCount > 1 ? 's' : ''} beyond... +
+
+ Complete challenges to unlock new missions +
+
+ `; + } + } // End of progressionEnabled else block + } + + // Custom levels section + if (customLevels.size > 0) { + html += ` +
+

Custom Levels

+
+ `; + + for (const [name, config] of customLevels.entries()) { + const timestamp = config.timestamp ? new Date(config.timestamp).toLocaleDateString() : ''; + const description = config.metadata?.description || `${config.asteroids.length} asteroids • ${config.planets.length} planets`; + const author = config.metadata?.author || 'Unknown'; + + html += ` +
+

${name}

+
+ Difficulty: ${config.difficulty} • By ${author} +
+

${description}

+ ${timestamp ? `
Created ${timestamp}
` : ''} + +
+ `; + } + } + + // Editor unlock button (always unlocked if progression disabled) + const isEditorUnlocked = !progressionEnabled || progression.isEditorUnlocked(); + const completedCount = progression.getCompletedCount(); + + html += ` +
+ ${isEditorUnlocked ? ` + + 🎨 Create Custom Level + + ` : ` +
+ 🔒 Level Editor (Complete ${3 - completedCount} more level${(3 - completedCount) !== 1 ? 's' : ''}) +
+ `} +
+ `; + container.innerHTML = html; // Add event listeners to level buttons diff --git a/src/levelSerializer.ts b/src/levelSerializer.ts index 0a3c28b..75a6c00 100644 --- a/src/levelSerializer.ts +++ b/src/levelSerializer.ts @@ -100,7 +100,7 @@ export class LevelSerializer { } /** - * Serialize start base state + * Serialize start base state (position and GLB paths) */ private serializeStartBase(): StartBaseConfig { const startBase = this.scene.getMeshByName("startBase"); @@ -109,31 +109,18 @@ export class LevelSerializer { console.warn("Start base not found, using defaults"); return { position: [0, 0, 0], - diameter: 10, - height: 1, - color: [1, 1, 0] + baseGlbPath: 'base.glb' }; } const position = this.vector3ToArray(startBase.position); - // Try to extract diameter and height from scaling or metadata - // Assuming cylinder was created with specific dimensions - const diameter = 10; // Default from Level1 - const height = 1; // Default from Level1 - - // Get color from material if available - let color: Vector3Array = [1, 1, 0]; // Default yellow - if (startBase.material && (startBase.material as any).diffuseColor) { - const diffuseColor = (startBase.material as any).diffuseColor; - color = [diffuseColor.r, diffuseColor.g, diffuseColor.b]; - } + // Capture GLB path from metadata if available, otherwise use default + const baseGlbPath = startBase.metadata?.baseGlbPath || 'base.glb'; return { position, - diameter, - height, - color + baseGlbPath }; } @@ -191,7 +178,7 @@ export class LevelSerializer { const diameter = boundingInfo.boundingSphere.radiusWorld * 2; // Get texture path from material - let texturePath = "/planetTextures/Arid/Arid_01-512x512.png"; // Default + let texturePath = "/assets/materials/planetTextures/Arid/Arid_01-512x512.png"; // Default if (mesh.material && (mesh.material as any).diffuseTexture) { const texture = (mesh.material as any).diffuseTexture; texturePath = texture.url || texturePath; @@ -222,7 +209,8 @@ export class LevelSerializer { for (const mesh of asteroidMeshes) { const position = this.vector3ToArray(mesh.position); - const scaling = this.vector3ToArray(mesh.scaling); + // Use uniform scale (assume uniform scaling, take x component) + const scale = parseFloat(mesh.scaling.x.toFixed(3)); // Get velocities from physics body let linearVelocity: Vector3Array = [0, 0, 0]; @@ -238,7 +226,7 @@ export class LevelSerializer { asteroids.push({ id: mesh.name, position, - scaling, + scale, linearVelocity, angularVelocity, mass diff --git a/src/main.ts b/src/main.ts index d28b387..667b8d4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -118,6 +118,12 @@ export class Main { // Listen for replay requests from the ship if (ship) { + // Set current level name for progression tracking + if (ship._statusScreen) { + ship._statusScreen.setCurrentLevel(levelName); + debugLog(`Set current level for progression: ${levelName}`); + } + ship.onReplayRequestObservable.add(() => { debugLog('Replay requested - reloading page'); window.location.reload(); @@ -472,16 +478,10 @@ export class Main { // Setup router router.on('/', () => { - // Check if there are saved levels - if (!hasSavedLevels()) { - debugLog('No saved levels found, redirecting to editor'); - router.navigate('/editor'); - return; - } - + // Always show game view with level selector (no editor redirect) showView('game'); - // Populate level selector + // Populate level selector (will show default levels if no custom levels) populateLevelSelector(); // Initialize game if not in debug mode diff --git a/src/planetTextures.ts b/src/planetTextures.ts index d25d671..3537c0b 100644 --- a/src/planetTextures.ts +++ b/src/planetTextures.ts @@ -5,103 +5,103 @@ export const PLANET_TEXTURES = [ // Arid planets (5 textures) - "/planetTextures/Arid/Arid_01-512x512.png", - "/planetTextures/Arid/Arid_02-512x512.png", - "/planetTextures/Arid/Arid_03-512x512.png", - "/planetTextures/Arid/Arid_04-512x512.png", - "/planetTextures/Arid/Arid_05-512x512.png", + "/assets/materials/planetTextures/Arid/Arid_01-512x512.png", + "/assets/materials/planetTextures/Arid/Arid_02-512x512.png", + "/assets/materials/planetTextures/Arid/Arid_03-512x512.png", + "/assets/materials/planetTextures/Arid/Arid_04-512x512.png", + "/assets/materials/planetTextures/Arid/Arid_05-512x512.png", // Barren planets (5 textures) - "/planetTextures/Barren/Barren_01-512x512.png", - "/planetTextures/Barren/Barren_02-512x512.png", - "/planetTextures/Barren/Barren_03-512x512.png", - "/planetTextures/Barren/Barren_04-512x512.png", - "/planetTextures/Barren/Barren_05-512x512.png", + "/assets/materials/planetTextures/Barren/Barren_01-512x512.png", + "/assets/materials/planetTextures/Barren/Barren_02-512x512.png", + "/assets/materials/planetTextures/Barren/Barren_03-512x512.png", + "/assets/materials/planetTextures/Barren/Barren_04-512x512.png", + "/assets/materials/planetTextures/Barren/Barren_05-512x512.png", // Dusty planets (5 textures) - "/planetTextures/Dusty/Dusty_01-512x512.png", - "/planetTextures/Dusty/Dusty_02-512x512.png", - "/planetTextures/Dusty/Dusty_03-512x512.png", - "/planetTextures/Dusty/Dusty_04-512x512.png", - "/planetTextures/Dusty/Dusty_05-512x512.png", + "/assets/materials/planetTextures/Dusty/Dusty_01-512x512.png", + "/assets/materials/planetTextures/Dusty/Dusty_02-512x512.png", + "/assets/materials/planetTextures/Dusty/Dusty_03-512x512.png", + "/assets/materials/planetTextures/Dusty/Dusty_04-512x512.png", + "/assets/materials/planetTextures/Dusty/Dusty_05-512x512.png", // Gaseous planets (20 textures) - "/planetTextures/Gaseous/Gaseous_01-512x512.png", - "/planetTextures/Gaseous/Gaseous_02-512x512.png", - "/planetTextures/Gaseous/Gaseous_03-512x512.png", - "/planetTextures/Gaseous/Gaseous_04-512x512.png", - "/planetTextures/Gaseous/Gaseous_05-512x512.png", - "/planetTextures/Gaseous/Gaseous_06-512x512.png", - "/planetTextures/Gaseous/Gaseous_07-512x512.png", - "/planetTextures/Gaseous/Gaseous_08-512x512.png", - "/planetTextures/Gaseous/Gaseous_09-512x512.png", - "/planetTextures/Gaseous/Gaseous_10-512x512.png", - "/planetTextures/Gaseous/Gaseous_11-512x512.png", - "/planetTextures/Gaseous/Gaseous_12-512x512.png", - "/planetTextures/Gaseous/Gaseous_13-512x512.png", - "/planetTextures/Gaseous/Gaseous_14-512x512.png", - "/planetTextures/Gaseous/Gaseous_15-512x512.png", - "/planetTextures/Gaseous/Gaseous_16-512x512.png", - "/planetTextures/Gaseous/Gaseous_17-512x512.png", - "/planetTextures/Gaseous/Gaseous_18-512x512.png", - "/planetTextures/Gaseous/Gaseous_19-512x512.png", - "/planetTextures/Gaseous/Gaseous_20-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_01-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_02-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_03-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_04-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_05-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_06-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_07-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_08-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_09-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_10-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_11-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_12-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_13-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_14-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_15-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_16-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_17-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_18-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_19-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_20-512x512.png", // Grassland planets (5 textures) - "/planetTextures/Grassland/Grassland_01-512x512.png", - "/planetTextures/Grassland/Grassland_02-512x512.png", - "/planetTextures/Grassland/Grassland_03-512x512.png", - "/planetTextures/Grassland/Grassland_04-512x512.png", - "/planetTextures/Grassland/Grassland_05-512x512.png", + "/assets/materials/planetTextures/Grassland/Grassland_01-512x512.png", + "/assets/materials/planetTextures/Grassland/Grassland_02-512x512.png", + "/assets/materials/planetTextures/Grassland/Grassland_03-512x512.png", + "/assets/materials/planetTextures/Grassland/Grassland_04-512x512.png", + "/assets/materials/planetTextures/Grassland/Grassland_05-512x512.png", // Jungle planets (5 textures) - "/planetTextures/Jungle/Jungle_01-512x512.png", - "/planetTextures/Jungle/Jungle_02-512x512.png", - "/planetTextures/Jungle/Jungle_03-512x512.png", - "/planetTextures/Jungle/Jungle_04-512x512.png", - "/planetTextures/Jungle/Jungle_05-512x512.png", + "/assets/materials/planetTextures/Jungle/Jungle_01-512x512.png", + "/assets/materials/planetTextures/Jungle/Jungle_02-512x512.png", + "/assets/materials/planetTextures/Jungle/Jungle_03-512x512.png", + "/assets/materials/planetTextures/Jungle/Jungle_04-512x512.png", + "/assets/materials/planetTextures/Jungle/Jungle_05-512x512.png", // Marshy planets (5 textures) - "/planetTextures/Marshy/Marshy_01-512x512.png", - "/planetTextures/Marshy/Marshy_02-512x512.png", - "/planetTextures/Marshy/Marshy_03-512x512.png", - "/planetTextures/Marshy/Marshy_04-512x512.png", - "/planetTextures/Marshy/Marshy_05-512x512.png", + "/assets/materials/planetTextures/Marshy/Marshy_01-512x512.png", + "/assets/materials/planetTextures/Marshy/Marshy_02-512x512.png", + "/assets/materials/planetTextures/Marshy/Marshy_03-512x512.png", + "/assets/materials/planetTextures/Marshy/Marshy_04-512x512.png", + "/assets/materials/planetTextures/Marshy/Marshy_05-512x512.png", // Martian planets (5 textures) - "/planetTextures/Martian/Martian_01-512x512.png", - "/planetTextures/Martian/Martian_02-512x512.png", - "/planetTextures/Martian/Martian_03-512x512.png", - "/planetTextures/Martian/Martian_04-512x512.png", - "/planetTextures/Martian/Martian_05-512x512.png", + "/assets/materials/planetTextures/Martian/Martian_01-512x512.png", + "/assets/materials/planetTextures/Martian/Martian_02-512x512.png", + "/assets/materials/planetTextures/Martian/Martian_03-512x512.png", + "/assets/materials/planetTextures/Martian/Martian_04-512x512.png", + "/assets/materials/planetTextures/Martian/Martian_05-512x512.png", // Methane planets (5 textures) - "/planetTextures/Methane/Methane_01-512x512.png", - "/planetTextures/Methane/Methane_02-512x512.png", - "/planetTextures/Methane/Methane_03-512x512.png", - "/planetTextures/Methane/Methane_04-512x512.png", - "/planetTextures/Methane/Methane_05-512x512.png", + "/assets/materials/planetTextures/Methane/Methane_01-512x512.png", + "/assets/materials/planetTextures/Methane/Methane_02-512x512.png", + "/assets/materials/planetTextures/Methane/Methane_03-512x512.png", + "/assets/materials/planetTextures/Methane/Methane_04-512x512.png", + "/assets/materials/planetTextures/Methane/Methane_05-512x512.png", // Sandy planets (5 textures) - "/planetTextures/Sandy/Sandy_01-512x512.png", - "/planetTextures/Sandy/Sandy_02-512x512.png", - "/planetTextures/Sandy/Sandy_03-512x512.png", - "/planetTextures/Sandy/Sandy_04-512x512.png", - "/planetTextures/Sandy/Sandy_05-512x512.png", + "/assets/materials/planetTextures/Sandy/Sandy_01-512x512.png", + "/assets/materials/planetTextures/Sandy/Sandy_02-512x512.png", + "/assets/materials/planetTextures/Sandy/Sandy_03-512x512.png", + "/assets/materials/planetTextures/Sandy/Sandy_04-512x512.png", + "/assets/materials/planetTextures/Sandy/Sandy_05-512x512.png", // Snowy planets (5 textures) - "/planetTextures/Snowy/Snowy_01-512x512.png", - "/planetTextures/Snowy/Snowy_02-512x512.png", - "/planetTextures/Snowy/Snowy_03-512x512.png", - "/planetTextures/Snowy/Snowy_04-512x512.png", - "/planetTextures/Snowy/Snowy_05-512x512.png", + "/assets/materials/planetTextures/Snowy/Snowy_01-512x512.png", + "/assets/materials/planetTextures/Snowy/Snowy_02-512x512.png", + "/assets/materials/planetTextures/Snowy/Snowy_03-512x512.png", + "/assets/materials/planetTextures/Snowy/Snowy_04-512x512.png", + "/assets/materials/planetTextures/Snowy/Snowy_05-512x512.png", // Tundra planets (5 textures) - "/planetTextures/Tundra/Tundra_01-512x512.png", - "/planetTextures/Tundra/Tundra_02-512x512.png", - "/planetTextures/Tundra/Tundra_03-512x512.png", - "/planetTextures/Tundra/Tundra_04-512x512.png", - "/planetTextures/Tundra/Tundral-EQUIRECTANGULAR-5-512x512.png", + "/assets/materials/planetTextures/Tundra/Tundra_01-512x512.png", + "/assets/materials/planetTextures/Tundra/Tundra_02-512x512.png", + "/assets/materials/planetTextures/Tundra/Tundra_03-512x512.png", + "/assets/materials/planetTextures/Tundra/Tundra_04-512x512.png", + "/assets/materials/planetTextures/Tundra/Tundral-EQUIRECTANGULAR-5-512x512.png", ]; /** @@ -116,103 +116,103 @@ export function getRandomPlanetTexture(): string { */ export const PLANET_TEXTURES_BY_TYPE = { arid: [ - "/planetTextures/Arid/Arid_01-512x512.png", - "/planetTextures/Arid/Arid_02-512x512.png", - "/planetTextures/Arid/Arid_03-512x512.png", - "/planetTextures/Arid/Arid_04-512x512.png", - "/planetTextures/Arid/Arid_05-512x512.png", + "/assets/materials/planetTextures/Arid/Arid_01-512x512.png", + "/assets/materials/planetTextures/Arid/Arid_02-512x512.png", + "/assets/materials/planetTextures/Arid/Arid_03-512x512.png", + "/assets/materials/planetTextures/Arid/Arid_04-512x512.png", + "/assets/materials/planetTextures/Arid/Arid_05-512x512.png", ], barren: [ - "/planetTextures/Barren/Barren_01-512x512.png", - "/planetTextures/Barren/Barren_02-512x512.png", - "/planetTextures/Barren/Barren_03-512x512.png", - "/planetTextures/Barren/Barren_04-512x512.png", - "/planetTextures/Barren/Barren_05-512x512.png", + "/assets/materials/planetTextures/Barren/Barren_01-512x512.png", + "/assets/materials/planetTextures/Barren/Barren_02-512x512.png", + "/assets/materials/planetTextures/Barren/Barren_03-512x512.png", + "/assets/materials/planetTextures/Barren/Barren_04-512x512.png", + "/assets/materials/planetTextures/Barren/Barren_05-512x512.png", ], dusty: [ - "/planetTextures/Dusty/Dusty_01-512x512.png", - "/planetTextures/Dusty/Dusty_02-512x512.png", - "/planetTextures/Dusty/Dusty_03-512x512.png", - "/planetTextures/Dusty/Dusty_04-512x512.png", - "/planetTextures/Dusty/Dusty_05-512x512.png", + "/assets/materials/planetTextures/Dusty/Dusty_01-512x512.png", + "/assets/materials/planetTextures/Dusty/Dusty_02-512x512.png", + "/assets/materials/planetTextures/Dusty/Dusty_03-512x512.png", + "/assets/materials/planetTextures/Dusty/Dusty_04-512x512.png", + "/assets/materials/planetTextures/Dusty/Dusty_05-512x512.png", ], gaseous: [ - "/planetTextures/Gaseous/Gaseous_01-512x512.png", - "/planetTextures/Gaseous/Gaseous_02-512x512.png", - "/planetTextures/Gaseous/Gaseous_03-512x512.png", - "/planetTextures/Gaseous/Gaseous_04-512x512.png", - "/planetTextures/Gaseous/Gaseous_05-512x512.png", - "/planetTextures/Gaseous/Gaseous_06-512x512.png", - "/planetTextures/Gaseous/Gaseous_07-512x512.png", - "/planetTextures/Gaseous/Gaseous_08-512x512.png", - "/planetTextures/Gaseous/Gaseous_09-512x512.png", - "/planetTextures/Gaseous/Gaseous_10-512x512.png", - "/planetTextures/Gaseous/Gaseous_11-512x512.png", - "/planetTextures/Gaseous/Gaseous_12-512x512.png", - "/planetTextures/Gaseous/Gaseous_13-512x512.png", - "/planetTextures/Gaseous/Gaseous_14-512x512.png", - "/planetTextures/Gaseous/Gaseous_15-512x512.png", - "/planetTextures/Gaseous/Gaseous_16-512x512.png", - "/planetTextures/Gaseous/Gaseous_17-512x512.png", - "/planetTextures/Gaseous/Gaseous_18-512x512.png", - "/planetTextures/Gaseous/Gaseous_19-512x512.png", - "/planetTextures/Gaseous/Gaseous_20-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_01-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_02-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_03-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_04-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_05-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_06-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_07-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_08-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_09-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_10-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_11-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_12-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_13-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_14-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_15-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_16-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_17-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_18-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_19-512x512.png", + "/assets/materials/planetTextures/Gaseous/Gaseous_20-512x512.png", ], grassland: [ - "/planetTextures/Grassland/Grassland_01-512x512.png", - "/planetTextures/Grassland/Grassland_02-512x512.png", - "/planetTextures/Grassland/Grassland_03-512x512.png", - "/planetTextures/Grassland/Grassland_04-512x512.png", - "/planetTextures/Grassland/Grassland_05-512x512.png", + "/assets/materials/planetTextures/Grassland/Grassland_01-512x512.png", + "/assets/materials/planetTextures/Grassland/Grassland_02-512x512.png", + "/assets/materials/planetTextures/Grassland/Grassland_03-512x512.png", + "/assets/materials/planetTextures/Grassland/Grassland_04-512x512.png", + "/assets/materials/planetTextures/Grassland/Grassland_05-512x512.png", ], jungle: [ - "/planetTextures/Jungle/Jungle_01-512x512.png", - "/planetTextures/Jungle/Jungle_02-512x512.png", - "/planetTextures/Jungle/Jungle_03-512x512.png", - "/planetTextures/Jungle/Jungle_04-512x512.png", - "/planetTextures/Jungle/Jungle_05-512x512.png", + "/assets/materials/planetTextures/Jungle/Jungle_01-512x512.png", + "/assets/materials/planetTextures/Jungle/Jungle_02-512x512.png", + "/assets/materials/planetTextures/Jungle/Jungle_03-512x512.png", + "/assets/materials/planetTextures/Jungle/Jungle_04-512x512.png", + "/assets/materials/planetTextures/Jungle/Jungle_05-512x512.png", ], marshy: [ - "/planetTextures/Marshy/Marshy_01-512x512.png", - "/planetTextures/Marshy/Marshy_02-512x512.png", - "/planetTextures/Marshy/Marshy_03-512x512.png", - "/planetTextures/Marshy/Marshy_04-512x512.png", - "/planetTextures/Marshy/Marshy_05-512x512.png", + "/assets/materials/planetTextures/Marshy/Marshy_01-512x512.png", + "/assets/materials/planetTextures/Marshy/Marshy_02-512x512.png", + "/assets/materials/planetTextures/Marshy/Marshy_03-512x512.png", + "/assets/materials/planetTextures/Marshy/Marshy_04-512x512.png", + "/assets/materials/planetTextures/Marshy/Marshy_05-512x512.png", ], martian: [ - "/planetTextures/Martian/Martian_01-512x512.png", - "/planetTextures/Martian/Martian_02-512x512.png", - "/planetTextures/Martian/Martian_03-512x512.png", - "/planetTextures/Martian/Martian_04-512x512.png", - "/planetTextures/Martian/Martian_05-512x512.png", + "/assets/materials/planetTextures/Martian/Martian_01-512x512.png", + "/assets/materials/planetTextures/Martian/Martian_02-512x512.png", + "/assets/materials/planetTextures/Martian/Martian_03-512x512.png", + "/assets/materials/planetTextures/Martian/Martian_04-512x512.png", + "/assets/materials/planetTextures/Martian/Martian_05-512x512.png", ], methane: [ - "/planetTextures/Methane/Methane_01-512x512.png", - "/planetTextures/Methane/Methane_02-512x512.png", - "/planetTextures/Methane/Methane_03-512x512.png", - "/planetTextures/Methane/Methane_04-512x512.png", - "/planetTextures/Methane/Methane_05-512x512.png", + "/assets/materials/planetTextures/Methane/Methane_01-512x512.png", + "/assets/materials/planetTextures/Methane/Methane_02-512x512.png", + "/assets/materials/planetTextures/Methane/Methane_03-512x512.png", + "/assets/materials/planetTextures/Methane/Methane_04-512x512.png", + "/assets/materials/planetTextures/Methane/Methane_05-512x512.png", ], sandy: [ - "/planetTextures/Sandy/Sandy_01-512x512.png", - "/planetTextures/Sandy/Sandy_02-512x512.png", - "/planetTextures/Sandy/Sandy_03-512x512.png", - "/planetTextures/Sandy/Sandy_04-512x512.png", - "/planetTextures/Sandy/Sandy_05-512x512.png", + "/assets/materials/planetTextures/Sandy/Sandy_01-512x512.png", + "/assets/materials/planetTextures/Sandy/Sandy_02-512x512.png", + "/assets/materials/planetTextures/Sandy/Sandy_03-512x512.png", + "/assets/materials/planetTextures/Sandy/Sandy_04-512x512.png", + "/assets/materials/planetTextures/Sandy/Sandy_05-512x512.png", ], snowy: [ - "/planetTextures/Snowy/Snowy_01-512x512.png", - "/planetTextures/Snowy/Snowy_02-512x512.png", - "/planetTextures/Snowy/Snowy_03-512x512.png", - "/planetTextures/Snowy/Snowy_04-512x512.png", - "/planetTextures/Snowy/Snowy_05-512x512.png", + "/assets/materials/planetTextures/Snowy/Snowy_01-512x512.png", + "/assets/materials/planetTextures/Snowy/Snowy_02-512x512.png", + "/assets/materials/planetTextures/Snowy/Snowy_03-512x512.png", + "/assets/materials/planetTextures/Snowy/Snowy_04-512x512.png", + "/assets/materials/planetTextures/Snowy/Snowy_05-512x512.png", ], tundra: [ - "/planetTextures/Tundra/Tundra_01-512x512.png", - "/planetTextures/Tundra/Tundra_02-512x512.png", - "/planetTextures/Tundra/Tundra_03-512x512.png", - "/planetTextures/Tundra/Tundra_04-512x512.png", - "/planetTextures/Tundra/Tundral-EQUIRECTANGULAR-5-512x512.png", + "/assets/materials/planetTextures/Tundra/Tundra_01-512x512.png", + "/assets/materials/planetTextures/Tundra/Tundra_02-512x512.png", + "/assets/materials/planetTextures/Tundra/Tundra_03-512x512.png", + "/assets/materials/planetTextures/Tundra/Tundra_04-512x512.png", + "/assets/materials/planetTextures/Tundra/Tundral-EQUIRECTANGULAR-5-512x512.png", ], }; diff --git a/src/progression.ts b/src/progression.ts new file mode 100644 index 0000000..bc75997 --- /dev/null +++ b/src/progression.ts @@ -0,0 +1,266 @@ +/** + * Progression tracking system for level completion and feature unlocks + */ + +export interface LevelProgress { + levelName: string; + completed: boolean; + completedAt?: string; // ISO timestamp + bestTime?: number; // Best completion time in seconds + bestAccuracy?: number; // Best accuracy percentage + playCount: number; +} + +export interface ProgressionData { + version: string; + completedLevels: Map; + editorUnlocked: boolean; + firstPlayDate?: string; + lastPlayDate?: string; +} + +const STORAGE_KEY = 'space-game-progress'; +const PROGRESSION_VERSION = '1.0'; +const EDITOR_UNLOCK_REQUIREMENT = 3; // Complete 3 default levels to unlock editor + +/** + * Progression manager - tracks level completion and unlocks + */ +export class ProgressionManager { + private static _instance: ProgressionManager; + private _data: ProgressionData; + + private constructor() { + this._data = this.loadProgress(); + } + + public static getInstance(): ProgressionManager { + if (!ProgressionManager._instance) { + ProgressionManager._instance = new ProgressionManager(); + } + return ProgressionManager._instance; + } + + /** + * Load progression data from localStorage + */ + private loadProgress(): ProgressionData { + try { + const stored = localStorage.getItem(STORAGE_KEY); + if (stored) { + const parsed = JSON.parse(stored); + // Convert completedLevels array back to Map + const completedLevels = new Map( + parsed.completedLevels || [] + ); + return { + version: parsed.version || PROGRESSION_VERSION, + completedLevels, + editorUnlocked: parsed.editorUnlocked || false, + firstPlayDate: parsed.firstPlayDate, + lastPlayDate: parsed.lastPlayDate + }; + } + } catch (error) { + console.error('Error loading progression data:', error); + } + + // Return fresh progression data + return { + version: PROGRESSION_VERSION, + completedLevels: new Map(), + editorUnlocked: false, + firstPlayDate: new Date().toISOString() + }; + } + + /** + * Save progression data to localStorage + */ + private saveProgress(): void { + try { + // Convert Map to array for JSON serialization + const toSave = { + ...this._data, + completedLevels: Array.from(this._data.completedLevels.entries()), + lastPlayDate: new Date().toISOString() + }; + localStorage.setItem(STORAGE_KEY, JSON.stringify(toSave)); + } catch (error) { + console.error('Error saving progression data:', error); + } + } + + /** + * Mark a level as completed with optional stats + */ + public markLevelComplete( + levelName: string, + stats?: { + completionTime?: number; + accuracy?: number; + } + ): void { + const existing = this._data.completedLevels.get(levelName); + const now = new Date().toISOString(); + + const progress: LevelProgress = { + levelName, + completed: true, + completedAt: now, + bestTime: stats?.completionTime, + bestAccuracy: stats?.accuracy, + playCount: (existing?.playCount || 0) + 1 + }; + + // Update best time if this is better + if (existing?.bestTime && stats?.completionTime) { + progress.bestTime = Math.min(existing.bestTime, stats.completionTime); + } + + // Update best accuracy if this is better + if (existing?.bestAccuracy && stats?.accuracy) { + progress.bestAccuracy = Math.max(existing.bestAccuracy, stats.accuracy); + } + + this._data.completedLevels.set(levelName, progress); + + // Check if editor should be unlocked + this.checkEditorUnlock(); + + this.saveProgress(); + } + + /** + * Record that a level was started (for play count) + */ + public recordLevelStart(levelName: string): void { + const existing = this._data.completedLevels.get(levelName); + if (!existing) { + this._data.completedLevels.set(levelName, { + levelName, + completed: false, + playCount: 1 + }); + } + this.saveProgress(); + } + + /** + * Check if a level has been completed + */ + public isLevelComplete(levelName: string): boolean { + return this._data.completedLevels.get(levelName)?.completed || false; + } + + /** + * Get progress data for a specific level + */ + public getLevelProgress(levelName: string): LevelProgress | undefined { + return this._data.completedLevels.get(levelName); + } + + /** + * Get all completed default levels + */ + public getCompletedDefaultLevels(): string[] { + const defaultLevels = this.getDefaultLevelNames(); + return defaultLevels.filter(name => this.isLevelComplete(name)); + } + + /** + * Get the next incomplete default level + */ + public getNextLevel(): string | null { + const defaultLevels = this.getDefaultLevelNames(); + for (const levelName of defaultLevels) { + if (!this.isLevelComplete(levelName)) { + return levelName; + } + } + return null; // All levels completed + } + + /** + * Get list of default level names in order + */ + private getDefaultLevelNames(): string[] { + return [ + 'Tutorial: Asteroid Field', + 'Rescue Mission', + 'Deep Space Patrol', + 'Enemy Territory', + 'The Gauntlet', + 'Final Challenge' + ]; + } + + /** + * Check if editor should be unlocked based on completion + */ + private checkEditorUnlock(): void { + const completedCount = this.getCompletedDefaultLevels().length; + if (completedCount >= EDITOR_UNLOCK_REQUIREMENT && !this._data.editorUnlocked) { + this._data.editorUnlocked = true; + console.log(`🎉 Editor unlocked! (${completedCount} levels completed)`); + } + } + + /** + * Check if the level editor is unlocked + */ + public isEditorUnlocked(): boolean { + return this._data.editorUnlocked; + } + + /** + * Get count of completed default levels + */ + public getCompletedCount(): number { + return this.getCompletedDefaultLevels().length; + } + + /** + * Get total count of default levels + */ + public getTotalDefaultLevels(): number { + return this.getDefaultLevelNames().length; + } + + /** + * Get completion percentage + */ + public getCompletionPercentage(): number { + const total = this.getTotalDefaultLevels(); + const completed = this.getCompletedCount(); + return total > 0 ? (completed / total) * 100 : 0; + } + + /** + * Reset all progression (for testing or user request) + */ + public reset(): void { + this._data = { + version: PROGRESSION_VERSION, + completedLevels: new Map(), + editorUnlocked: false, + firstPlayDate: new Date().toISOString() + }; + this.saveProgress(); + } + + /** + * Force unlock editor (admin/testing) + */ + public forceUnlockEditor(): void { + this._data.editorUnlocked = true; + this.saveProgress(); + } + + /** + * Get all progression data (for display/debugging) + */ + public getAllProgress(): ProgressionData { + return { ...this._data }; + } +} diff --git a/src/rockFactory.ts b/src/rockFactory.ts index 34e771d..052cefb 100644 --- a/src/rockFactory.ts +++ b/src/rockFactory.ts @@ -98,12 +98,12 @@ export class RockFactory { debugLog(this._asteroidMesh); } - public static async createRock(i: number, position: Vector3, size: Vector3, + public static async createRock(i: number, position: Vector3, scale: number, linearVelocitry: Vector3, angularVelocity: Vector3, score: Observable): Promise { const rock = new InstancedMesh("asteroid-" +i, this._asteroidMesh as Mesh); debugLog(rock.id); - rock.scaling = size; + rock.scaling = new Vector3(scale, scale, scale); rock.position = position; //rock.material = this._rockMaterial; rock.name = "asteroid-" + i; diff --git a/src/ship.ts b/src/ship.ts index db5f1fa..2f8e125 100644 --- a/src/ship.ts +++ b/src/ship.ts @@ -314,7 +314,8 @@ export class Ship { this._gameStats, () => this.handleReplayRequest(), () => this.handleExitVR(), - () => this.handleResume() + () => this.handleResume(), + () => this.handleNextLevel() ); this._statusScreen.initialize(this._camera); } @@ -345,6 +346,16 @@ export class Ship { this._controllerInput?.setEnabled(true); } + /** + * Handle next level button click from status screen + */ + private handleNextLevel(): void { + debugLog('Next Level button clicked - navigating to level selector'); + // Navigate back to level selector (root route) + window.location.hash = '#/'; + window.location.reload(); + } + /** * Check game-ending conditions and auto-show status screen * Conditions: @@ -375,7 +386,7 @@ export class Ship { // Check condition 1: Death by hull damage (outside landing zone) if (!this._isInLandingZone && hull < 0.01) { debugLog('Game end condition met: Hull critical outside landing zone'); - this._statusScreen.show(true); + this._statusScreen.show(true, false); // Game ended, not victory this._keyboardInput?.setEnabled(false); this._controllerInput?.setEnabled(false); this._statusScreenAutoShown = true; @@ -385,7 +396,7 @@ export class Ship { // Check condition 2: Stranded (outside landing zone, no fuel, low velocity) if (!this._isInLandingZone && fuel < 0.01 && totalVelocity < 1) { debugLog('Game end condition met: Stranded (no fuel, low velocity)'); - this._statusScreen.show(true); + this._statusScreen.show(true, false); // Game ended, not victory this._keyboardInput?.setEnabled(false); this._controllerInput?.setEnabled(false); this._statusScreenAutoShown = true; @@ -395,7 +406,7 @@ export class Ship { // Check condition 3: Victory (all asteroids destroyed, inside landing zone) if (asteroidsRemaining <= 0 && this._isInLandingZone) { debugLog('Game end condition met: Victory (all asteroids destroyed)'); - this._statusScreen.show(true); + this._statusScreen.show(true, true); // Game ended, VICTORY! this._keyboardInput?.setEnabled(false); this._controllerInput?.setEnabled(false); this._statusScreenAutoShown = true; diff --git a/src/starBase.ts b/src/starBase.ts index 4ec5850..98ac583 100644 --- a/src/starBase.ts +++ b/src/starBase.ts @@ -10,6 +10,7 @@ import {DefaultScene} from "./defaultScene"; import {GameConfig} from "./gameConfig"; import debugLog from "./debug"; import loadAsset from "./utils/loadAsset"; +import {Vector3Array} from "./levelConfig"; export interface StarBaseResult { baseMesh: AbstractMesh; @@ -18,18 +19,30 @@ export interface StarBaseResult { /** * Create and load the star base mesh - * @param position - Position for the star base + * @param position - Position for the star base (defaults to [0, 0, 0]) + * @param baseGlbPath - Path to the base GLB file (defaults to 'base.glb') * @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(position?: Vector3Array, baseGlbPath: string = 'base.glb'): Promise { const config = GameConfig.getInstance(); const scene = DefaultScene.MainScene; - const importMeshes = await loadAsset('base.glb'); + const importMeshes = await loadAsset(baseGlbPath); const baseMesh = importMeshes.meshes.get('Base'); const landingMesh = importMeshes.meshes.get('BaseLandingZone'); + // Store the GLB path in metadata for serialization + if (baseMesh) { + baseMesh.metadata = baseMesh.metadata || {}; + baseMesh.metadata.baseGlbPath = baseGlbPath; + } + + // Apply position to both meshes (defaults to [0, 0, 0]) + const pos = position ? new Vector3(position[0], position[1], position[2]) : new Vector3(0, 0, 0); + baseMesh.position = pos.clone(); + landingMesh.position = pos.clone(); + let landingAgg: PhysicsAggregate | null = null; if (config.physicsEnabled) { diff --git a/src/statusScreen.ts b/src/statusScreen.ts index d98ad9c..4da5217 100644 --- a/src/statusScreen.ts +++ b/src/statusScreen.ts @@ -16,6 +16,7 @@ import { } from "@babylonjs/core"; import { GameStats } from "./gameStats"; import { DefaultScene } from "./defaultScene"; +import { ProgressionManager } from "./progression"; /** * Status screen that displays game statistics @@ -41,21 +42,27 @@ export class StatusScreen { private _replayButton: Button; private _exitButton: Button; private _resumeButton: Button; + private _nextLevelButton: Button; // Callbacks private _onReplayCallback: (() => void) | null = null; private _onExitCallback: (() => void) | null = null; private _onResumeCallback: (() => void) | null = null; + private _onNextLevelCallback: (() => void) | null = null; // Track whether game has ended private _isGameEnded: boolean = false; - constructor(scene: Scene, gameStats: GameStats, onReplay?: () => void, onExit?: () => void, onResume?: () => void) { + // Track current level name for progression + private _currentLevelName: string | null = null; + + constructor(scene: Scene, gameStats: GameStats, onReplay?: () => void, onExit?: () => void, onResume?: () => void, onNextLevel?: () => void) { this._scene = scene; this._gameStats = gameStats; this._onReplayCallback = onReplay || null; this._onExitCallback = onExit || null; this._onResumeCallback = onResume || null; + this._onNextLevelCallback = onNextLevel || null; } /** @@ -154,8 +161,25 @@ export class StatusScreen { }); buttonBar.addControl(this._resumeButton); + // Create Next Level button (only shown when game has ended and there's a next level) + this._nextLevelButton = Button.CreateSimpleButton("nextLevelButton", "NEXT LEVEL"); + this._nextLevelButton.width = "300px"; + this._nextLevelButton.height = "60px"; + this._nextLevelButton.color = "white"; + this._nextLevelButton.background = "#0088ff"; + this._nextLevelButton.cornerRadius = 10; + this._nextLevelButton.thickness = 0; + this._nextLevelButton.fontSize = "30px"; + this._nextLevelButton.fontWeight = "bold"; + this._nextLevelButton.onPointerClickObservable.add(() => { + if (this._onNextLevelCallback) { + this._onNextLevelCallback(); + } + }); + buttonBar.addControl(this._nextLevelButton); + // Create Replay button (only shown when game has ended) - this._replayButton = Button.CreateSimpleButton("replayButton", "REPLAY LEVEL"); + this._replayButton = Button.CreateSimpleButton("replayButton", "REPLAY"); this._replayButton.width = "300px"; this._replayButton.height = "60px"; this._replayButton.color = "white"; @@ -279,11 +303,19 @@ export class StatusScreen { } } + /** + * Set the current level name for progression tracking + */ + public setCurrentLevel(levelName: string): void { + this._currentLevelName = levelName; + } + /** * Show the status screen * @param isGameEnded - true if game has ended (death/stranded/victory), false if manually paused + * @param victory - true if the level was completed successfully */ - public show(isGameEnded: boolean = false): void { + public show(isGameEnded: boolean = false, victory: boolean = false): void { if (!this._screenMesh) { return; } @@ -291,6 +323,21 @@ export class StatusScreen { // Store game ended state this._isGameEnded = isGameEnded; + // Mark level as complete if victory and we have a level name + const progression = ProgressionManager.getInstance(); + if (victory && this._currentLevelName) { + const stats = this._gameStats.getStats(); + const gameTimeSeconds = this.parseGameTime(stats.gameTime); + progression.markLevelComplete(this._currentLevelName, { + completionTime: gameTimeSeconds, + accuracy: stats.accuracy // Already a number from getAccuracy() + }); + } + + // Determine if there's a next level + const nextLevel = progression.getNextLevel(); + const hasNextLevel = nextLevel !== null; + // Show/hide appropriate buttons based on whether game has ended if (this._resumeButton) { this._resumeButton.isVisible = !isGameEnded; @@ -298,6 +345,10 @@ export class StatusScreen { if (this._replayButton) { this._replayButton.isVisible = isGameEnded; } + if (this._nextLevelButton) { + // Only show Next Level if game ended in victory and there's a next level + this._nextLevelButton.isVisible = isGameEnded && victory && hasNextLevel; + } // Enable pointer selection for button interaction this.enablePointerSelection(); @@ -310,6 +361,19 @@ export class StatusScreen { this._isVisible = true; } + /** + * Parse game time string (MM:SS) to seconds + */ + private parseGameTime(timeString: string): number { + const parts = timeString.split(':'); + if (parts.length === 2) { + const minutes = parseInt(parts[0], 10); + const seconds = parseInt(parts[1], 10); + return minutes * 60 + seconds; + } + return 0; + } + /** * Hide the status screen */