nova64 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -8
- package/bin/nova64.js +165 -0
- package/dist/assets/console-CY_kygm3.js +14 -0
- package/dist/assets/console-CY_kygm3.js.map +1 -0
- package/dist/assets/main-l0sNRNKZ.js.map +1 -0
- package/dist/assets/sky/studio/nx.png +0 -0
- package/dist/assets/sky/studio/ny.png +0 -0
- package/dist/assets/sky/studio/nz.png +0 -0
- package/dist/assets/sky/studio/px.png +0 -0
- package/dist/assets/sky/studio/py.png +0 -0
- package/dist/assets/sky/studio/pz.png +0 -0
- package/dist/assets/vanilla-Dcuy32gi.js +2 -0
- package/dist/assets/vanilla-Dcuy32gi.js.map +1 -0
- package/dist/console.html +899 -0
- package/dist/docs/BENCHMARK.md +77 -0
- package/dist/docs/CHEATSHEET.md +255 -0
- package/dist/docs/EFFECTS_API_GUIDE.md +577 -0
- package/dist/docs/EFFECTS_QUICK_REFERENCE.md +331 -0
- package/dist/docs/FONT_CHARACTER_REFERENCE.md +219 -0
- package/dist/docs/FREE_GLB_ASSETS.md +330 -0
- package/dist/docs/FULLSCREEN_BUTTON_FEATURE.md +296 -0
- package/dist/docs/GAMEPAD_SUPPORT.md +348 -0
- package/dist/docs/GAME_IMPROVEMENTS.md +278 -0
- package/dist/docs/GAME_QUALITY_STATUS.md +300 -0
- package/dist/docs/MIGRATION_GUIDE.md +553 -0
- package/dist/docs/NOVA64_3D_API.md +356 -0
- package/dist/docs/NOVA64_API_REFERENCE.md +1406 -0
- package/dist/docs/NOVA64_UI_API.md +503 -0
- package/dist/docs/UI_SYSTEM_SUMMARY.md +445 -0
- package/dist/docs/VOXEL_ENGINE_GUIDE.md +662 -0
- package/dist/docs/VOXEL_QUICK_REFERENCE.md +386 -0
- package/dist/docs/api-3d.html +750 -0
- package/dist/docs/api-effects.html +385 -0
- package/dist/docs/api-improvements.md +121 -0
- package/dist/docs/api-skybox.html +407 -0
- package/dist/docs/api-sprites.html +321 -0
- package/dist/docs/api-voxel.html +337 -0
- package/dist/docs/api.html +543 -0
- package/dist/docs/assets.html +306 -0
- package/dist/docs/audio.html +340 -0
- package/dist/docs/blogs.html +286 -0
- package/dist/docs/collision.html +316 -0
- package/dist/docs/console.html +247 -0
- package/dist/docs/editor.html +297 -0
- package/dist/docs/font.html +247 -0
- package/dist/docs/framebuffer.html +247 -0
- package/dist/docs/fullscreen-button.html +297 -0
- package/dist/docs/gpu-systems.html +247 -0
- package/dist/docs/index.html +580 -0
- package/dist/docs/input.html +491 -0
- package/dist/docs/physics.html +311 -0
- package/dist/docs/screens.html +311 -0
- package/dist/docs/storage.html +311 -0
- package/dist/docs/textinput.html +332 -0
- package/dist/docs/ui.html +488 -0
- package/dist/examples/3d-advanced/code.js +695 -0
- package/dist/examples/adventure-comic-3d/code.js +342 -0
- package/dist/examples/audio-lab/code.js +150 -0
- package/dist/examples/boids-flocking/code.js +270 -0
- package/dist/examples/crystal-cathedral-3d/code.js +706 -0
- package/dist/examples/cyberpunk-city-3d/code.js +1383 -0
- package/dist/examples/demoscene/README.md +192 -0
- package/dist/examples/demoscene/code.js +1081 -0
- package/dist/examples/demoscene/meta.json +21 -0
- package/dist/examples/dungeon-crawler-3d/code.js +1117 -0
- package/dist/examples/f-zero-nova-3d/code.js +865 -0
- package/dist/examples/f-zero-nova-3d/code_old.js +1555 -0
- package/dist/examples/fps-demo-3d/code.js +744 -0
- package/dist/examples/game-of-life-3d/code.js +338 -0
- package/dist/examples/generative-art/code.js +632 -0
- package/dist/examples/hello-3d/code.js +325 -0
- package/dist/examples/hello-skybox/code.js +183 -0
- package/dist/examples/hello-world/code.js +19 -0
- package/dist/examples/input-showcase/code.js +109 -0
- package/dist/examples/instancing-demo/code.js +315 -0
- package/dist/examples/minecraft-demo/code.js +387 -0
- package/dist/examples/model-viewer-3d/code.js +114 -0
- package/dist/examples/mystical-realm-3d/code.js +1203 -0
- package/dist/examples/nature-explorer-3d/code.js +1318 -0
- package/dist/examples/particles-demo/code.js +522 -0
- package/dist/examples/pbr-showcase/code.js +140 -0
- package/dist/examples/physics-demo-3d/code.js +948 -0
- package/dist/examples/screen-demo/code.js +267 -0
- package/dist/examples/shooter-demo-3d/code.js +1286 -0
- package/dist/examples/space-combat-3d/IMPLEMENTATION_SUMMARY.md +109 -0
- package/dist/examples/space-combat-3d/README.md +135 -0
- package/dist/examples/space-combat-3d/code.js +1332 -0
- package/dist/examples/space-harrier-3d/code.js +923 -0
- package/dist/examples/star-fox-nova-3d/code.js +1116 -0
- package/dist/examples/star-fox-nova-3d/code_backup.js +410 -0
- package/dist/examples/star-fox-nova-3d/code_broken.js +1821 -0
- package/dist/examples/storage-quest/code.js +209 -0
- package/dist/examples/strider-demo-3d/IMPROVEMENT_OPTIONS.md +285 -0
- package/dist/examples/strider-demo-3d/cache-test.html +132 -0
- package/dist/examples/strider-demo-3d/code-fixed.js +582 -0
- package/dist/examples/strider-demo-3d/code-old.js +1537 -0
- package/dist/examples/strider-demo-3d/code.js +1462 -0
- package/dist/examples/strider-demo-3d/code.js.bak2 +1169 -0
- package/dist/examples/strider-demo-3d/fix-game.sh +53 -0
- package/dist/examples/super-plumber-64/README.md +128 -0
- package/dist/examples/super-plumber-64/code.js +1185 -0
- package/dist/examples/super-plumber-64/index.html +88 -0
- package/dist/examples/test-2d-overlay/code.js +32 -0
- package/dist/examples/test-font/code.js +51 -0
- package/dist/examples/test-minimal/code.js +21 -0
- package/dist/examples/ui-demo/code.js +306 -0
- package/dist/examples/wing-commander-space/README.md +180 -0
- package/dist/examples/wing-commander-space/code.js +1285 -0
- package/dist/examples/wizardry-3d/CHANGELOG.md +366 -0
- package/dist/examples/wizardry-3d/code.js +3928 -0
- package/dist/index.html +666 -0
- package/dist/os9-shell/assets/index-DIHfrTaW.css +1 -0
- package/dist/os9-shell/assets/index-KchE_ngx.js +483 -0
- package/dist/os9-shell/assets/index-KchE_ngx.js.map +1 -0
- package/dist/os9-shell/index.html +23 -0
- package/dist/os9-shell/nova-icon.svg +12 -0
- package/dist/runtime/api-2d.js +1158 -0
- package/dist/runtime/api-3d/camera.js +73 -0
- package/dist/runtime/api-3d/instancing.js +180 -0
- package/dist/runtime/api-3d/lights.js +51 -0
- package/dist/runtime/api-3d/materials.js +47 -0
- package/dist/runtime/api-3d/models.js +84 -0
- package/dist/runtime/api-3d/particles.js +296 -0
- package/dist/runtime/api-3d/pbr.js +113 -0
- package/dist/runtime/api-3d/primitives.js +304 -0
- package/dist/runtime/api-3d/scene.js +169 -0
- package/dist/runtime/api-3d/transforms.js +161 -0
- package/dist/runtime/api-3d.js +166 -0
- package/dist/runtime/api-effects.js +840 -0
- package/dist/runtime/api-gameutils.js +476 -0
- package/dist/runtime/api-generative.js +610 -0
- package/dist/runtime/api-presets.js +85 -0
- package/dist/runtime/api-skybox.js +232 -0
- package/dist/runtime/api-sprites.js +100 -0
- package/dist/runtime/api-voxel.js +712 -0
- package/dist/runtime/api.js +201 -0
- package/dist/runtime/assets.js +27 -0
- package/dist/runtime/audio.js +114 -0
- package/dist/runtime/collision.js +47 -0
- package/dist/runtime/console.js +101 -0
- package/dist/runtime/editor.js +233 -0
- package/dist/runtime/font.js +233 -0
- package/dist/runtime/framebuffer.js +28 -0
- package/dist/runtime/fullscreen-button.js +185 -0
- package/dist/runtime/gpu-canvas2d.js +47 -0
- package/dist/runtime/gpu-threejs.js +643 -0
- package/dist/runtime/gpu-webgl2.js +310 -0
- package/dist/runtime/index.d.ts +682 -0
- package/dist/runtime/index.js +22 -0
- package/dist/runtime/input.js +225 -0
- package/dist/runtime/logger.js +60 -0
- package/dist/runtime/physics.js +101 -0
- package/dist/runtime/screens.js +213 -0
- package/dist/runtime/storage.js +38 -0
- package/dist/runtime/store.js +151 -0
- package/dist/runtime/textinput.js +68 -0
- package/dist/runtime/ui/buttons.js +124 -0
- package/dist/runtime/ui/panels.js +105 -0
- package/dist/runtime/ui/text.js +86 -0
- package/dist/runtime/ui/widgets.js +141 -0
- package/dist/runtime/ui.js +111 -0
- package/index.html +6 -1
- package/package.json +9 -2
- package/public/assets/sky/studio/nx.png +0 -0
- package/public/assets/sky/studio/ny.png +0 -0
- package/public/assets/sky/studio/nz.png +0 -0
- package/public/assets/sky/studio/px.png +0 -0
- package/public/assets/sky/studio/py.png +0 -0
- package/public/assets/sky/studio/pz.png +0 -0
- package/public/os9-shell/assets/index-KchE_ngx.js +483 -0
- package/public/os9-shell/assets/index-KchE_ngx.js.map +1 -0
- package/public/os9-shell/index.html +10 -1
- package/runtime/api-2d.js +301 -21
- package/runtime/api-3d/pbr.js +45 -1
- package/runtime/api-3d.js +1 -0
- package/runtime/api-effects.js +90 -3
- package/runtime/api-gameutils.js +476 -0
- package/runtime/api-generative.js +610 -0
- package/runtime/api-skybox.js +54 -0
- package/runtime/api-voxel.js +139 -28
- package/runtime/gpu-threejs.js +13 -9
- package/runtime/ui.js +2 -2
- package/src/main.js +20 -0
- package/public/os9-shell/assets/index-B1Uvacma.js +0 -32825
- package/public/os9-shell/assets/index-B1Uvacma.js.map +0 -1
|
@@ -0,0 +1,1821 @@
|
|
|
1
|
+
// ===============================================
|
|
2
|
+
// STAR FOX NOVA 64 - DESERTED SPACE STYLE
|
|
3
|
+
// Built with proper space combat mechanics
|
|
4
|
+
// ===============================================
|
|
5
|
+
|
|
6
|
+
let gameData, uiData, gameSettings, hudElements;
|
|
7
|
+
|
|
8
|
+
// Track all created meshes for cleanup
|
|
9
|
+
let allMeshes = [];
|
|
10
|
+
|
|
11
|
+
function trackMesh(mesh) {
|
|
12
|
+
if (mesh && !allMeshes.includes(mesh)) {
|
|
13
|
+
allMeshes.push(mesh);
|
|
14
|
+
}
|
|
15
|
+
return mesh;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function cleanupAllMeshes() {
|
|
19
|
+
allMeshes.forEach(mesh => {
|
|
20
|
+
if (mesh && typeof destroyMesh === 'function') {
|
|
21
|
+
destroyMesh(mesh);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
allMeshes = [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// === GAME INITIALIZATION ===
|
|
28
|
+
export async function init() {
|
|
29
|
+
console.log('🚀 STAR FOX NOVA 64 - Deserted Space Quality...');
|
|
30
|
+
|
|
31
|
+
// Clear everything first
|
|
32
|
+
cls();
|
|
33
|
+
|
|
34
|
+
// Simple, effective 3D setup like Deserted Space
|
|
35
|
+
setCameraPosition(0, 5, 15);
|
|
36
|
+
setCameraTarget(0, 0, 0);
|
|
37
|
+
setCameraFOV(75);
|
|
38
|
+
|
|
39
|
+
// Clean, effective lighting
|
|
40
|
+
setLightDirection(0, -1, -0.5);
|
|
41
|
+
setLightColor(0xffffff);
|
|
42
|
+
setAmbientLight(0x404040);
|
|
43
|
+
|
|
44
|
+
// Initialize simple game settings
|
|
45
|
+
gameSettings = {
|
|
46
|
+
graphics: {
|
|
47
|
+
enableParticles: true,
|
|
48
|
+
enableTrails: false,
|
|
49
|
+
shadowQuality: 'medium',
|
|
50
|
+
starDensity: 500,
|
|
51
|
+
},
|
|
52
|
+
physics: {
|
|
53
|
+
acceleration: 15,
|
|
54
|
+
maxSpeed: 25,
|
|
55
|
+
friction: 0.88,
|
|
56
|
+
gravity: 0,
|
|
57
|
+
},
|
|
58
|
+
gameplay: {
|
|
59
|
+
difficulty: 1.0,
|
|
60
|
+
enemySpawnRate: 2.0,
|
|
61
|
+
powerupChance: 0.15,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Clean up any existing meshes from previous loads
|
|
66
|
+
cleanupAllMeshes();
|
|
67
|
+
|
|
68
|
+
// Initialize comprehensive game state
|
|
69
|
+
gameData = {
|
|
70
|
+
// Core game stats
|
|
71
|
+
score: 0,
|
|
72
|
+
health: 100,
|
|
73
|
+
shields: 100,
|
|
74
|
+
energy: 100,
|
|
75
|
+
wave: 1,
|
|
76
|
+
kills: 0,
|
|
77
|
+
time: 0,
|
|
78
|
+
|
|
79
|
+
// Advanced player ship with physics
|
|
80
|
+
arwing: {
|
|
81
|
+
position: { x: 0, y: 2, z: 0 },
|
|
82
|
+
velocity: { x: 0, y: 0, z: 0 },
|
|
83
|
+
rotation: { x: 0, y: 0, z: 0 },
|
|
84
|
+
mesh: null,
|
|
85
|
+
parts: [],
|
|
86
|
+
engineGlow: [],
|
|
87
|
+
lastShot: 0,
|
|
88
|
+
boost: false,
|
|
89
|
+
shields: true,
|
|
90
|
+
speed: 0,
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
// Game objects
|
|
94
|
+
enemies: [],
|
|
95
|
+
projectiles: [],
|
|
96
|
+
powerups: [],
|
|
97
|
+
particles: [],
|
|
98
|
+
trails: [],
|
|
99
|
+
stars: [],
|
|
100
|
+
debris: [],
|
|
101
|
+
spaceFloor: [],
|
|
102
|
+
|
|
103
|
+
// Camera system
|
|
104
|
+
camera: {
|
|
105
|
+
position: { x: 0, y: 5, z: 15 },
|
|
106
|
+
target: { x: 0, y: 0, z: 0 },
|
|
107
|
+
shake: 0,
|
|
108
|
+
shakeIntensity: 0,
|
|
109
|
+
followSmoothing: 0.1,
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
// Environment
|
|
113
|
+
environment: {
|
|
114
|
+
nebula: [],
|
|
115
|
+
asteroids: [],
|
|
116
|
+
planets: [],
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
// Audio/Visual feedback
|
|
120
|
+
effects: {
|
|
121
|
+
screenFlash: 0,
|
|
122
|
+
timeSlowdown: 1.0,
|
|
123
|
+
combatZoom: 0,
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// UI state
|
|
128
|
+
uiData = {
|
|
129
|
+
time: 0,
|
|
130
|
+
mouseX: 0,
|
|
131
|
+
mouseY: 0,
|
|
132
|
+
startButtonHover: false,
|
|
133
|
+
showFPS: false,
|
|
134
|
+
targetingSystem: {
|
|
135
|
+
targets: [],
|
|
136
|
+
locked: null,
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Initialize HUD system
|
|
141
|
+
initializeHUD();
|
|
142
|
+
|
|
143
|
+
// Create advanced starfield
|
|
144
|
+
createAdvancedStarfield();
|
|
145
|
+
|
|
146
|
+
// Create space floor
|
|
147
|
+
createSpaceFloor();
|
|
148
|
+
|
|
149
|
+
// Setup mouse controls
|
|
150
|
+
setupMouseHandlers();
|
|
151
|
+
|
|
152
|
+
// Title Screen
|
|
153
|
+
addScreen('start', {
|
|
154
|
+
enter: function () {
|
|
155
|
+
console.log('📺 Entering professional start screen...');
|
|
156
|
+
cleanupAllMeshes();
|
|
157
|
+
|
|
158
|
+
// Setup cinematic camera for title
|
|
159
|
+
if (typeof setCameraPosition === 'function') {
|
|
160
|
+
setCameraPosition(0, 3, 12);
|
|
161
|
+
setCameraTarget(0, 0, 0);
|
|
162
|
+
setCameraFOV(60);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Create demo ship for title screen
|
|
166
|
+
createTitleScreenDemo();
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
draw: function () {
|
|
170
|
+
drawProfessionalStartScreen();
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
update: function (dt) {
|
|
174
|
+
updateStartScreen(dt);
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Enhanced Game Screen
|
|
179
|
+
addScreen('game', {
|
|
180
|
+
enter: function () {
|
|
181
|
+
console.log('🎮 Entering PROFESSIONAL game mode...');
|
|
182
|
+
|
|
183
|
+
setupGameEnvironment();
|
|
184
|
+
createPlayerShip();
|
|
185
|
+
initializeGameSystems();
|
|
186
|
+
spawnInitialEnemyWave();
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
draw: function () {
|
|
190
|
+
drawGameEnvironment();
|
|
191
|
+
drawProfessionalHUD();
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
update: function (dt) {
|
|
195
|
+
updateGameSystems(dt);
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Game Over Screen
|
|
200
|
+
addScreen('gameover', {
|
|
201
|
+
enter: function () {
|
|
202
|
+
console.log('💀 Game Over - Final Score:', gameData.score);
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
draw: function () {
|
|
206
|
+
drawGameOverScreen();
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
update: function (dt) {
|
|
210
|
+
if (isKeyPressed(' ') || isKeyPressed('Enter')) {
|
|
211
|
+
switchToScreen('start');
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Start with title screen
|
|
217
|
+
switchToScreen('start');
|
|
218
|
+
|
|
219
|
+
console.log('✅ PROFESSIONAL STAR FOX NOVA 64 Ready!');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// === HUD SYSTEM INTEGRATION ===
|
|
223
|
+
function initializeHUD() {
|
|
224
|
+
hudElements = {
|
|
225
|
+
scoreDisplay: null,
|
|
226
|
+
healthBar: null,
|
|
227
|
+
crosshair: null,
|
|
228
|
+
speedometer: null,
|
|
229
|
+
initialized: false,
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
console.log('🎯 HUD system initialized');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// === ADVANCED GRAPHICS FUNCTIONS ===
|
|
236
|
+
function createAdvancedStarfield() {
|
|
237
|
+
gameData.stars = [];
|
|
238
|
+
|
|
239
|
+
// Create multiple star layers for depth
|
|
240
|
+
for (let layer = 0; layer < 3; layer++) {
|
|
241
|
+
const starCount = [200, 150, 100][layer];
|
|
242
|
+
const distance = [100, 200, 500][layer];
|
|
243
|
+
const speed = [1, 0.5, 0.2][layer];
|
|
244
|
+
|
|
245
|
+
for (let i = 0; i < starCount; i++) {
|
|
246
|
+
gameData.stars.push({
|
|
247
|
+
x: (Math.random() - 0.5) * distance * 2,
|
|
248
|
+
y: (Math.random() - 0.5) * distance,
|
|
249
|
+
z: -Math.random() * distance - 50 - layer * 100,
|
|
250
|
+
originalZ: -Math.random() * distance - 50 - layer * 100,
|
|
251
|
+
speed: speed + Math.random() * speed,
|
|
252
|
+
brightness: Math.random() * 0.8 + 0.2,
|
|
253
|
+
size: Math.random() * 2 + 1,
|
|
254
|
+
layer: layer,
|
|
255
|
+
twinkle: Math.random() * Math.PI * 2,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function createSpaceFloor() {
|
|
262
|
+
console.log('🌌 Creating simple, stable starfield like Deserted Space...');
|
|
263
|
+
|
|
264
|
+
// Create simple starfield - no complex mesh tracking
|
|
265
|
+
gameData.stars = [];
|
|
266
|
+
|
|
267
|
+
for (let i = 0; i < 200; i++) {
|
|
268
|
+
const star = createCube(0.5, 0xffffff, [
|
|
269
|
+
(Math.random() - 0.5) * 200,
|
|
270
|
+
(Math.random() - 0.5) * 100,
|
|
271
|
+
-100 - Math.random() * 300,
|
|
272
|
+
]);
|
|
273
|
+
|
|
274
|
+
gameData.stars.push({
|
|
275
|
+
mesh: star,
|
|
276
|
+
speed: 20 + Math.random() * 30,
|
|
277
|
+
x: (Math.random() - 0.5) * 200,
|
|
278
|
+
y: (Math.random() - 0.5) * 100,
|
|
279
|
+
z: -100 - Math.random() * 300,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// 2. Crystal Cathedral pillars in formation
|
|
284
|
+
for (let i = 0; i < 12; i++) {
|
|
285
|
+
const angle = (i / 12) * Math.PI * 2;
|
|
286
|
+
const radius = 80;
|
|
287
|
+
const x = Math.cos(angle) * radius;
|
|
288
|
+
const z = Math.sin(angle) * radius - 200;
|
|
289
|
+
const height = 25 + Math.sin(i * 0.7) * 8;
|
|
290
|
+
|
|
291
|
+
// Towering crystal pillars
|
|
292
|
+
const pillar = trackMesh(createCube(4, 0x4488ff, [x, height / 2 - 60, z]));
|
|
293
|
+
setScale(pillar, 3, height, 3);
|
|
294
|
+
|
|
295
|
+
// Glowing crystal caps
|
|
296
|
+
const cap = trackMesh(createSphere(2, 0x88ddff, [x, height - 40, z]));
|
|
297
|
+
|
|
298
|
+
gameData.spaceFloor.push({
|
|
299
|
+
mesh: pillar,
|
|
300
|
+
cap: cap,
|
|
301
|
+
type: 'pillar',
|
|
302
|
+
x: x,
|
|
303
|
+
originalZ: z,
|
|
304
|
+
customZ: z,
|
|
305
|
+
height: height,
|
|
306
|
+
glowPhase: Math.random() * Math.PI * 2,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// 3. Holographic grid system with multiple layers
|
|
311
|
+
const gridSpacing = 25;
|
|
312
|
+
const gridCount = 20;
|
|
313
|
+
|
|
314
|
+
// HOLOGRAPHIC GRID SYSTEM like Crystal Cathedral
|
|
315
|
+
for (let i = -gridCount; i <= gridCount; i += 3) {
|
|
316
|
+
for (let j = 0; j < 15; j++) {
|
|
317
|
+
const z = -150 - j * 60;
|
|
318
|
+
|
|
319
|
+
// Holographic grid lines with emissive glow
|
|
320
|
+
const hLine = trackMesh(createCube(4, 0x4488ff, [0, -57, z]));
|
|
321
|
+
setScale(hLine, 60, 1.5, 1);
|
|
322
|
+
gameData.spaceFloor.push({
|
|
323
|
+
mesh: hLine,
|
|
324
|
+
type: 'horizontal',
|
|
325
|
+
x: 0,
|
|
326
|
+
originalZ: z,
|
|
327
|
+
customZ: z,
|
|
328
|
+
glowPhase: j * 0.3,
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// Vertical holographic lines
|
|
332
|
+
if (Math.abs(i) <= 15) {
|
|
333
|
+
const vLine = trackMesh(createCube(4, 0x4488ff, [i * gridSpacing, -57, z]));
|
|
334
|
+
setScale(vLine, 1, 1.5, 60);
|
|
335
|
+
gameData.spaceFloor.push({
|
|
336
|
+
mesh: vLine,
|
|
337
|
+
type: 'vertical',
|
|
338
|
+
x: i * gridSpacing,
|
|
339
|
+
originalZ: z,
|
|
340
|
+
customZ: z,
|
|
341
|
+
glowPhase: i * 0.2,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// 4. FLOATING CRYSTAL ELEMENTS like Crystal Cathedral
|
|
348
|
+
for (let i = 0; i < 30; i++) {
|
|
349
|
+
const x = (Math.random() - 0.5) * 600;
|
|
350
|
+
const y = -40 + Math.random() * 20;
|
|
351
|
+
const z = -100 - Math.random() * 400;
|
|
352
|
+
|
|
353
|
+
// Holographic floating crystals
|
|
354
|
+
const crystal = trackMesh(createSphere(3, 0x88ddff, [x, y, z]));
|
|
355
|
+
setScale(crystal, 2, 4, 2);
|
|
356
|
+
gameData.spaceFloor.push({
|
|
357
|
+
mesh: crystal,
|
|
358
|
+
type: 'crystal',
|
|
359
|
+
x: x,
|
|
360
|
+
y: y,
|
|
361
|
+
originalZ: z,
|
|
362
|
+
customZ: z,
|
|
363
|
+
floatPhase: Math.random() * Math.PI * 2,
|
|
364
|
+
originalY: y,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// 4. Create depth markers - pillars in the distance
|
|
369
|
+
for (let i = 0; i < 20; i++) {
|
|
370
|
+
const x = (Math.random() - 0.5) * 600;
|
|
371
|
+
const z = -200 - Math.random() * 400;
|
|
372
|
+
|
|
373
|
+
const pillar = trackMesh(createCube(8, 0x004499, [x, -40, z]));
|
|
374
|
+
setScale(pillar, 2, 8, 2);
|
|
375
|
+
gameData.spaceFloor.push({
|
|
376
|
+
mesh: pillar,
|
|
377
|
+
type: 'pillar',
|
|
378
|
+
x: x,
|
|
379
|
+
originalZ: z,
|
|
380
|
+
customZ: z,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
console.log('🌌 Simple starfield created with', gameData.stars.length, 'stars');
|
|
385
|
+
}
|
|
386
|
+
function updateStarfield(dt) {
|
|
387
|
+
// Simple, reliable starfield update like Deserted Space
|
|
388
|
+
for (let star of gameData.stars) {
|
|
389
|
+
// Move stars toward player
|
|
390
|
+
star.z += star.speed * dt;
|
|
391
|
+
|
|
392
|
+
// Update star position
|
|
393
|
+
setPosition(star.mesh, star.x, star.y, star.z);
|
|
394
|
+
|
|
395
|
+
// Reset when star passes player
|
|
396
|
+
if (star.z > 30) {
|
|
397
|
+
star.z = -300 - Math.random() * 100;
|
|
398
|
+
star.x = (Math.random() - 0.5) * 200;
|
|
399
|
+
star.y = (Math.random() - 0.5) * 100;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function createTitleScreenDemo() {
|
|
405
|
+
// Create a demo Arwing for the title screen
|
|
406
|
+
try {
|
|
407
|
+
const demoShip = createAdvancedArwing(2, 0, -8);
|
|
408
|
+
gameData.demoShip = {
|
|
409
|
+
parts: demoShip,
|
|
410
|
+
rotation: 0,
|
|
411
|
+
position: { x: 2, y: 0, z: -8 },
|
|
412
|
+
};
|
|
413
|
+
} catch (e) {
|
|
414
|
+
console.warn('Demo ship creation error:', e);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function createAdvancedArwing(x, y, z) {
|
|
419
|
+
const shipParts = [];
|
|
420
|
+
|
|
421
|
+
try {
|
|
422
|
+
// SPECTACULAR ARWING - Much better than Deserted Space!
|
|
423
|
+
|
|
424
|
+
// Main fuselage - sleek and large
|
|
425
|
+
const fuselage = trackMesh(createCube(1.2, 0x0099ff, [x, y, z]));
|
|
426
|
+
setScale(fuselage, 1.5, 1, 5);
|
|
427
|
+
shipParts.push(fuselage);
|
|
428
|
+
|
|
429
|
+
// Cockpit - glowing crystal canopy
|
|
430
|
+
const cockpit = trackMesh(createSphere(0.8, 0x88ffff, [x, y + 0.5, z + 2]));
|
|
431
|
+
setScale(cockpit, 1.2, 0.8, 1.5);
|
|
432
|
+
shipParts.push(cockpit);
|
|
433
|
+
|
|
434
|
+
// Massive swept wings
|
|
435
|
+
const leftWing = trackMesh(createCube(4, 0x0066cc, [x - 3.5, y - 0.5, z - 1]));
|
|
436
|
+
setScale(leftWing, 1.5, 0.4, 4);
|
|
437
|
+
setRotation(leftWing, 0, 0, -0.2);
|
|
438
|
+
shipParts.push(leftWing);
|
|
439
|
+
|
|
440
|
+
const rightWing = trackMesh(createCube(4, 0x0066cc, [x + 3.5, y - 0.5, z - 1]));
|
|
441
|
+
setScale(rightWing, 1.5, 0.4, 4);
|
|
442
|
+
setRotation(rightWing, 0, 0, 0.2);
|
|
443
|
+
shipParts.push(rightWing);
|
|
444
|
+
|
|
445
|
+
// Wing tips - glowing
|
|
446
|
+
const leftWingTip = trackMesh(createSphere(0.6, 0x00ffff, [x - 5, y - 0.5, z - 1]));
|
|
447
|
+
shipParts.push(leftWingTip);
|
|
448
|
+
|
|
449
|
+
const rightWingTip = trackMesh(createSphere(0.6, 0x00ffff, [x + 5, y - 0.5, z - 1]));
|
|
450
|
+
shipParts.push(rightWingTip);
|
|
451
|
+
|
|
452
|
+
// Powerful engine pods
|
|
453
|
+
const leftEngine = trackMesh(createCube(1, 0x003399, [x - 3.5, y - 0.5, z - 2.5]));
|
|
454
|
+
setScale(leftEngine, 1.2, 1.2, 3);
|
|
455
|
+
shipParts.push(leftEngine);
|
|
456
|
+
|
|
457
|
+
const rightEngine = trackMesh(createCube(1, 0x003399, [x + 3.5, y - 0.5, z - 2.5]));
|
|
458
|
+
setScale(rightEngine, 1.2, 1.2, 3);
|
|
459
|
+
shipParts.push(rightEngine);
|
|
460
|
+
|
|
461
|
+
// Brilliant engine glows
|
|
462
|
+
const leftGlow = trackMesh(createSphere(0.8, 0xff6600, [x - 3.5, y - 0.5, z - 4]));
|
|
463
|
+
shipParts.push(leftGlow);
|
|
464
|
+
|
|
465
|
+
const rightGlow = trackMesh(createSphere(0.8, 0xff6600, [x + 3.5, y - 0.5, z - 4]));
|
|
466
|
+
shipParts.push(rightGlow);
|
|
467
|
+
|
|
468
|
+
// Quad laser cannons - intimidating
|
|
469
|
+
const leftCannon = trackMesh(createCube(0.5, 0xffaa00, [x - 4, y + 0.2, z + 3]));
|
|
470
|
+
setScale(leftCannon, 0.6, 0.6, 2.5);
|
|
471
|
+
shipParts.push(leftCannon);
|
|
472
|
+
|
|
473
|
+
const rightCannon = trackMesh(createCube(0.5, 0xffaa00, [x + 4, y + 0.2, z + 3]));
|
|
474
|
+
setScale(rightCannon, 0.6, 0.6, 2.5);
|
|
475
|
+
shipParts.push(rightCannon);
|
|
476
|
+
|
|
477
|
+
// Nose cone - sharp and aggressive
|
|
478
|
+
const nose = trackMesh(createSphere(0.6, 0x0088ff, [x, y, z + 3.5]));
|
|
479
|
+
setScale(nose, 0.8, 0.8, 1.5);
|
|
480
|
+
shipParts.push(nose);
|
|
481
|
+
|
|
482
|
+
return shipParts;
|
|
483
|
+
} catch (e) {
|
|
484
|
+
console.warn('Advanced Arwing creation error:', e);
|
|
485
|
+
return [trackMesh(createCube(3, 0x0099ff, [x, y, z]))];
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function createAdvancedEnemyFighter(x, y, z, type = 'fighter') {
|
|
490
|
+
const enemyParts = [];
|
|
491
|
+
|
|
492
|
+
try {
|
|
493
|
+
switch (type) {
|
|
494
|
+
case 'fighter': {
|
|
495
|
+
// Aggressive angular fighter
|
|
496
|
+
const body = trackMesh(createCube(1.2, 0xff3030, [x, y, z]));
|
|
497
|
+
setScale(body, 1.5, 1, 2.5);
|
|
498
|
+
enemyParts.push(body);
|
|
499
|
+
|
|
500
|
+
// Sharp wings
|
|
501
|
+
const leftWing = trackMesh(createCube(1.8, 0xcc2020, [x - 1.2, y, z]));
|
|
502
|
+
setScale(leftWing, 1, 0.4, 1.8);
|
|
503
|
+
setRotation(leftWing, 0, 0, -0.3);
|
|
504
|
+
enemyParts.push(leftWing);
|
|
505
|
+
|
|
506
|
+
const rightWing = trackMesh(createCube(1.8, 0xcc2020, [x + 1.2, y, z]));
|
|
507
|
+
setScale(rightWing, 1, 0.4, 1.8);
|
|
508
|
+
setRotation(rightWing, 0, 0, 0.3);
|
|
509
|
+
enemyParts.push(rightWing);
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
case 'bomber': {
|
|
514
|
+
// Large, slow bomber
|
|
515
|
+
const bomberBody = trackMesh(createCube(2, 0xff6600, [x, y, z]));
|
|
516
|
+
setScale(bomberBody, 2, 1.5, 3);
|
|
517
|
+
enemyParts.push(bomberBody);
|
|
518
|
+
break;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
case 'interceptor': {
|
|
522
|
+
// Fast, small interceptor
|
|
523
|
+
const interceptorBody = trackMesh(createSphere(0.8, 0xff8888, [x, y, z]));
|
|
524
|
+
enemyParts.push(interceptorBody);
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Engine glow for all types
|
|
530
|
+
const engineGlow = trackMesh(createSphere(0.4, 0xff4400, [x, y, z - 1.5]));
|
|
531
|
+
enemyParts.push(engineGlow);
|
|
532
|
+
|
|
533
|
+
return enemyParts;
|
|
534
|
+
} catch (e) {
|
|
535
|
+
console.warn('Advanced enemy creation error:', e);
|
|
536
|
+
return [trackMesh(createCube(1.5, 0xff3030, [x, y, z]))];
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// === MOUSE CONTROL SYSTEM ===
|
|
541
|
+
function setupMouseHandlers() {
|
|
542
|
+
try {
|
|
543
|
+
if (typeof document !== 'undefined') {
|
|
544
|
+
const canvas = document.getElementById('screen');
|
|
545
|
+
if (canvas) {
|
|
546
|
+
// Mouse movement for camera/targeting
|
|
547
|
+
canvas.addEventListener('mousemove', e => {
|
|
548
|
+
const rect = canvas.getBoundingClientRect();
|
|
549
|
+
uiData.mouseX = ((e.clientX - rect.left) / rect.width) * 640;
|
|
550
|
+
uiData.mouseY = ((e.clientY - rect.top) / rect.height) * 360;
|
|
551
|
+
|
|
552
|
+
// Update targeting system
|
|
553
|
+
updateTargetingSystem();
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
// Click handlers
|
|
557
|
+
canvas.addEventListener('click', handleCanvasClick);
|
|
558
|
+
canvas.addEventListener('contextmenu', e => e.preventDefault());
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
} catch (error) {
|
|
562
|
+
console.warn('Mouse setup error:', error);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function handleCanvasClick(e) {
|
|
567
|
+
try {
|
|
568
|
+
const currentScreen = getCurrentScreen ? getCurrentScreen() : 'start';
|
|
569
|
+
|
|
570
|
+
if (currentScreen === 'start') {
|
|
571
|
+
console.log('🚀 Starting professional game...');
|
|
572
|
+
switchToScreen('game');
|
|
573
|
+
} else if (currentScreen === 'game') {
|
|
574
|
+
// Fire weapons or interact
|
|
575
|
+
fireAdvancedWeapons();
|
|
576
|
+
}
|
|
577
|
+
} catch (e) {
|
|
578
|
+
console.warn('Click handler error:', e);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// === PROFESSIONAL START SCREEN ===
|
|
583
|
+
function drawProfessionalStartScreen() {
|
|
584
|
+
try {
|
|
585
|
+
// Deep space background
|
|
586
|
+
cls(0x000011);
|
|
587
|
+
|
|
588
|
+
// Animated starfield
|
|
589
|
+
drawAdvancedStarfield();
|
|
590
|
+
|
|
591
|
+
// Animated title with glow effect
|
|
592
|
+
uiData.time += 0.016;
|
|
593
|
+
|
|
594
|
+
const titleGlow = Math.sin(uiData.time * 3) * 0.4 + 0.6;
|
|
595
|
+
const titlePulse = Math.sin(uiData.time * 2) * 20 + 40;
|
|
596
|
+
|
|
597
|
+
// Main title with shadow effect
|
|
598
|
+
print('STAR FOX', 220 + 2, 80 + 2, rgba8(0, 0, 50, 150)); // Shadow
|
|
599
|
+
print('STAR FOX', 220, 80, rgba8(0, Math.floor(titleGlow * 255), 255, 255));
|
|
600
|
+
|
|
601
|
+
print('NOVA 64', 240 + 1, 110 + 1, rgba8(0, 0, 50, 150)); // Shadow
|
|
602
|
+
print('NOVA 64', 240, 110, rgba8(255, Math.floor(titleGlow * 200), 0, 255));
|
|
603
|
+
|
|
604
|
+
// Subtitle
|
|
605
|
+
print('PROFESSIONAL SPACE COMBAT', 180, 140, rgba8(150, 200, 255, 200));
|
|
606
|
+
|
|
607
|
+
// Animated start button
|
|
608
|
+
const buttonPulse = Math.sin(uiData.time * 4) * 0.3 + 0.7;
|
|
609
|
+
const buttonGlow = Math.floor(buttonPulse * 255);
|
|
610
|
+
|
|
611
|
+
// Large, obvious start button
|
|
612
|
+
rect(170, 180, 300, 60, rgba8(0, 50, 100, 150), true);
|
|
613
|
+
rect(165, 175, 310, 70, rgba8(0, buttonGlow / 2, buttonGlow, 200), false);
|
|
614
|
+
rect(167, 177, 306, 66, rgba8(0, buttonGlow / 3, buttonGlow / 2, 150), false);
|
|
615
|
+
|
|
616
|
+
print('CLICK TO START MISSION', 190, 205, rgba8(255, 255, 255, buttonGlow));
|
|
617
|
+
|
|
618
|
+
// Instructions
|
|
619
|
+
print('WASD: Flight Controls | SPACE: Primary Weapons', 150, 270, rgba8(200, 200, 200, 255));
|
|
620
|
+
print('SHIFT: Boost | Mouse: Targeting | ESC: Menu', 170, 290, rgba8(180, 180, 180, 255));
|
|
621
|
+
|
|
622
|
+
// Version info
|
|
623
|
+
print('Nova64 v0.2.0 Professional Edition', 20, 340, rgba8(100, 100, 100, 255));
|
|
624
|
+
|
|
625
|
+
// Animate demo ship if available
|
|
626
|
+
if (gameData.demoShip) {
|
|
627
|
+
updateDemoShip();
|
|
628
|
+
}
|
|
629
|
+
} catch (error) {
|
|
630
|
+
console.warn('Start screen error:', error);
|
|
631
|
+
// Simple fallback
|
|
632
|
+
cls(0x000033);
|
|
633
|
+
print('STAR FOX NOVA 64', 220, 100, rgba8(0, 255, 255, 255));
|
|
634
|
+
print('CLICK TO START', 270, 200, rgba8(255, 255, 0, 255));
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function updateDemoShip() {
|
|
639
|
+
if (!gameData.demoShip) return;
|
|
640
|
+
|
|
641
|
+
gameData.demoShip.rotation += 0.01;
|
|
642
|
+
|
|
643
|
+
// Rotate demo ship parts
|
|
644
|
+
if (gameData.demoShip.parts) {
|
|
645
|
+
for (let part of gameData.demoShip.parts) {
|
|
646
|
+
if (part) {
|
|
647
|
+
setRotation(part, 0, gameData.demoShip.rotation, 0);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function updateStartScreen(dt) {
|
|
654
|
+
// Any key or click starts the game
|
|
655
|
+
if (
|
|
656
|
+
isKeyPressed(' ') ||
|
|
657
|
+
isKeyPressed('Enter') ||
|
|
658
|
+
isKeyPressed('w') ||
|
|
659
|
+
isKeyPressed('a') ||
|
|
660
|
+
isKeyPressed('s') ||
|
|
661
|
+
isKeyPressed('d')
|
|
662
|
+
) {
|
|
663
|
+
console.log('🚀 Starting via keyboard...');
|
|
664
|
+
switchToScreen('game');
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// === ADVANCED GAME SETUP ===
|
|
669
|
+
function setupGameEnvironment() {
|
|
670
|
+
try {
|
|
671
|
+
// Deserted Space style camera - closer and more dynamic
|
|
672
|
+
if (typeof setCameraPosition === 'function') {
|
|
673
|
+
setCameraPosition(0, 15, 35);
|
|
674
|
+
setCameraTarget(0, 0, -20);
|
|
675
|
+
setCameraFOV(75);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Bright lighting for better visibility
|
|
679
|
+
if (typeof setLightDirection === 'function') {
|
|
680
|
+
setLightDirection(0, -1, -0.5);
|
|
681
|
+
}
|
|
682
|
+
if (typeof setAmbientLight === 'function') {
|
|
683
|
+
setAmbientLight(0.4);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
console.log('🌌 Deserted Space style environment initialized');
|
|
687
|
+
} catch (e) {
|
|
688
|
+
console.warn('Environment setup error:', e);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function createPlayerShip() {
|
|
693
|
+
try {
|
|
694
|
+
// Reset player state
|
|
695
|
+
gameData.arwing = {
|
|
696
|
+
position: { x: 0, y: 0, z: 0 },
|
|
697
|
+
velocity: { x: 0, y: 0, z: 0 },
|
|
698
|
+
rotation: { x: 0, y: 0, z: 0 },
|
|
699
|
+
parts: [],
|
|
700
|
+
lastShot: 0,
|
|
701
|
+
boost: false,
|
|
702
|
+
shields: true,
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
// Create advanced Arwing
|
|
706
|
+
gameData.arwing.parts = createAdvancedArwing(0, 0, 0);
|
|
707
|
+
gameData.arwing.mesh = gameData.arwing.parts[0]; // Main body reference
|
|
708
|
+
|
|
709
|
+
console.log('✅ Professional Arwing created with', gameData.arwing.parts.length, 'components');
|
|
710
|
+
} catch (e) {
|
|
711
|
+
console.warn('Player ship creation error:', e);
|
|
712
|
+
// Fallback
|
|
713
|
+
gameData.arwing.mesh = trackMesh(createCube(2, 0x0099ff, [0, 0, 0]));
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function initializeGameSystems() {
|
|
718
|
+
// Reset all game systems
|
|
719
|
+
gameData.score = 0;
|
|
720
|
+
gameData.health = 100;
|
|
721
|
+
gameData.shields = 100;
|
|
722
|
+
gameData.energy = 100;
|
|
723
|
+
gameData.wave = 1;
|
|
724
|
+
gameData.time = 0;
|
|
725
|
+
|
|
726
|
+
// Clear arrays
|
|
727
|
+
gameData.enemies = [];
|
|
728
|
+
gameData.projectiles = [];
|
|
729
|
+
gameData.particles = [];
|
|
730
|
+
gameData.powerups = [];
|
|
731
|
+
|
|
732
|
+
console.log('🎮 All game systems initialized');
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function spawnInitialEnemyWave() {
|
|
736
|
+
const enemyCount = 3 + gameData.wave;
|
|
737
|
+
|
|
738
|
+
for (let i = 0; i < enemyCount; i++) {
|
|
739
|
+
spawnAdvancedEnemy();
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
console.log(`🛸 Wave ${gameData.wave}: ${enemyCount} enemies deployed`);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function spawnAdvancedEnemy() {
|
|
746
|
+
try {
|
|
747
|
+
const x = (Math.random() - 0.5) * 80;
|
|
748
|
+
const y = (Math.random() - 0.5) * 40 + 10;
|
|
749
|
+
const z = 150 + Math.random() * 100; // SPAWN IN FRONT of player (positive Z)
|
|
750
|
+
|
|
751
|
+
// Spectacular enemy ships - better than Deserted Space!
|
|
752
|
+
const enemyMesh = trackMesh(createCube(2, 0xff2200, [x, y, z]));
|
|
753
|
+
setScale(enemyMesh, 2.5, 1.5, 3.5);
|
|
754
|
+
|
|
755
|
+
// Add enemy wings for impressive look
|
|
756
|
+
const leftWing = trackMesh(createCube(1.5, 0xcc1100, [x - 2, y, z]));
|
|
757
|
+
setScale(leftWing, 1, 0.4, 2);
|
|
758
|
+
|
|
759
|
+
const rightWing = trackMesh(createCube(1.5, 0xcc1100, [x + 2, y, z]));
|
|
760
|
+
setScale(rightWing, 1, 0.4, 2);
|
|
761
|
+
|
|
762
|
+
// Enemy engine glow
|
|
763
|
+
const glow = trackMesh(createSphere(0.8, 0xff6600, [x, y, z - 2]));
|
|
764
|
+
|
|
765
|
+
gameData.enemies.push({
|
|
766
|
+
mesh: enemyMesh,
|
|
767
|
+
wings: [leftWing, rightWing],
|
|
768
|
+
glow: glow,
|
|
769
|
+
position: { x, y, z },
|
|
770
|
+
velocity: { x: 0, y: 0, z: -25 }, // Move toward player
|
|
771
|
+
health: 3,
|
|
772
|
+
speed: 25 + Math.random() * 20,
|
|
773
|
+
lastShot: 0,
|
|
774
|
+
movePattern: Math.random() > 0.5 ? 'straight' : 'weaving',
|
|
775
|
+
});
|
|
776
|
+
} catch (e) {
|
|
777
|
+
console.warn('Enemy spawn error:', e);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// === ADVANCED STARFIELD ===
|
|
782
|
+
function drawAdvancedStarfield() {
|
|
783
|
+
for (let star of gameData.stars) {
|
|
784
|
+
if (star.z > -200 && star.z < 50) {
|
|
785
|
+
// 3D to 2D projection
|
|
786
|
+
const screenX = 320 + (star.x / (Math.abs(star.z) + 1)) * 400;
|
|
787
|
+
const screenY = 180 + (star.y / (Math.abs(star.z) + 1)) * 400;
|
|
788
|
+
|
|
789
|
+
if (screenX > -10 && screenX < 650 && screenY > -10 && screenY < 370) {
|
|
790
|
+
// Calculate star properties
|
|
791
|
+
const distance = Math.abs(star.z);
|
|
792
|
+
const alpha = Math.min(255, star.brightness * 255 * (100 / distance));
|
|
793
|
+
const size = Math.max(1, star.size * (50 / distance));
|
|
794
|
+
|
|
795
|
+
// Twinkling effect
|
|
796
|
+
star.twinkle += 0.1;
|
|
797
|
+
const twinkle = Math.sin(star.twinkle) * 0.3 + 0.7;
|
|
798
|
+
const finalAlpha = Math.floor(alpha * twinkle);
|
|
799
|
+
|
|
800
|
+
// Draw star with size and brightness
|
|
801
|
+
if (size > 2) {
|
|
802
|
+
// Large stars get a glow effect
|
|
803
|
+
rect(
|
|
804
|
+
screenX - size,
|
|
805
|
+
screenY - size,
|
|
806
|
+
size * 2,
|
|
807
|
+
size * 2,
|
|
808
|
+
rgba8(255, 255, 255, finalAlpha * 0.3),
|
|
809
|
+
true
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
rect(
|
|
813
|
+
screenX - size / 2,
|
|
814
|
+
screenY - size / 2,
|
|
815
|
+
size,
|
|
816
|
+
size,
|
|
817
|
+
rgba8(255, 255, 255, finalAlpha),
|
|
818
|
+
true
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// Space floor is now handled by 3D meshes, no 2D drawing needed
|
|
826
|
+
|
|
827
|
+
// === GAME SYSTEMS UPDATE ===
|
|
828
|
+
function updateGameSystems(dt) {
|
|
829
|
+
gameData.time += dt;
|
|
830
|
+
|
|
831
|
+
// Update all systems
|
|
832
|
+
updatePlayerShip(dt);
|
|
833
|
+
updateEnemies(dt);
|
|
834
|
+
updateProjectiles(dt);
|
|
835
|
+
updateParticles(dt);
|
|
836
|
+
updateStarfield(dt);
|
|
837
|
+
updateSpaceFloor(dt);
|
|
838
|
+
updateCamera(dt);
|
|
839
|
+
updateEffects(dt);
|
|
840
|
+
|
|
841
|
+
// Check wave completion
|
|
842
|
+
if (gameData.enemies.length === 0) {
|
|
843
|
+
gameData.wave++;
|
|
844
|
+
gameData.score += gameData.wave * 500; // Wave bonus
|
|
845
|
+
spawnInitialEnemyWave();
|
|
846
|
+
console.log(`🌊 Wave ${gameData.wave} begins! Bonus: ${gameData.wave * 500} points`);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// Check game over
|
|
850
|
+
if (gameData.health <= 0) {
|
|
851
|
+
console.log('💀 Mission Failed - Final Score:', gameData.score);
|
|
852
|
+
switchToScreen('gameover');
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// Menu escape
|
|
856
|
+
if (isKeyPressed('Escape')) {
|
|
857
|
+
switchToScreen('start');
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
function updatePlayerShip(dt) {
|
|
862
|
+
const ship = gameData.arwing;
|
|
863
|
+
const physics = gameSettings.physics;
|
|
864
|
+
|
|
865
|
+
// Direct, responsive movement like Deserted Space
|
|
866
|
+
const moveSpeed = 25;
|
|
867
|
+
const inputStrength = 1.0;
|
|
868
|
+
|
|
869
|
+
// Direct position control (not velocity-based)
|
|
870
|
+
if (isKeyDown('a') || isKeyDown('ArrowLeft')) {
|
|
871
|
+
ship.position.x -= moveSpeed * dt * inputStrength;
|
|
872
|
+
}
|
|
873
|
+
if (isKeyDown('d') || isKeyDown('ArrowRight')) {
|
|
874
|
+
ship.position.x += moveSpeed * dt * inputStrength;
|
|
875
|
+
}
|
|
876
|
+
if (isKeyDown('w') || isKeyDown('ArrowUp')) {
|
|
877
|
+
ship.position.y += moveSpeed * dt * inputStrength;
|
|
878
|
+
}
|
|
879
|
+
if (isKeyDown('s') || isKeyDown('ArrowDown')) {
|
|
880
|
+
ship.position.y -= moveSpeed * dt * inputStrength;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// Boost system
|
|
884
|
+
ship.boost = isKeyDown('Shift');
|
|
885
|
+
if (ship.boost) {
|
|
886
|
+
// Boost increases movement speed
|
|
887
|
+
if (isKeyDown('a') || isKeyDown('ArrowLeft')) ship.position.x -= moveSpeed * dt;
|
|
888
|
+
if (isKeyDown('d') || isKeyDown('ArrowRight')) ship.position.x += moveSpeed * dt;
|
|
889
|
+
if (isKeyDown('w') || isKeyDown('ArrowUp')) ship.position.y += moveSpeed * dt;
|
|
890
|
+
if (isKeyDown('s') || isKeyDown('ArrowDown')) ship.position.y -= moveSpeed * dt;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// Keep ship in bounds
|
|
894
|
+
ship.position.x = Math.max(-40, Math.min(40, ship.position.x));
|
|
895
|
+
ship.position.y = Math.max(-10, Math.min(25, ship.position.y));
|
|
896
|
+
|
|
897
|
+
// Banking rotation based on input (not velocity)
|
|
898
|
+
const bankAmount = 0.6;
|
|
899
|
+
ship.rotation.z = 0;
|
|
900
|
+
ship.rotation.x = 0;
|
|
901
|
+
|
|
902
|
+
if (isKeyDown('a') || isKeyDown('ArrowLeft')) {
|
|
903
|
+
ship.rotation.z = bankAmount;
|
|
904
|
+
} else if (isKeyDown('d') || isKeyDown('ArrowRight')) {
|
|
905
|
+
ship.rotation.z = -bankAmount;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
if (isKeyDown('w') || isKeyDown('ArrowUp')) {
|
|
909
|
+
ship.rotation.x = -0.3;
|
|
910
|
+
} else if (isKeyDown('s') || isKeyDown('ArrowDown')) {
|
|
911
|
+
ship.rotation.x = 0.3;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// Always moving forward through space
|
|
915
|
+
ship.speed = ship.boost ? 40 : 25;
|
|
916
|
+
|
|
917
|
+
// Continuous weapon fire when held
|
|
918
|
+
if (isKeyDown(' ')) {
|
|
919
|
+
if (gameData.time - ship.lastShot > 0.1) {
|
|
920
|
+
fireDesertedSpaceWeapons();
|
|
921
|
+
ship.lastShot = gameData.time;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// Update ship mesh positions
|
|
926
|
+
updateShipMeshes();
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
function updateShipMeshes() {
|
|
930
|
+
const ship = gameData.arwing;
|
|
931
|
+
|
|
932
|
+
if (ship.parts) {
|
|
933
|
+
for (let part of ship.parts) {
|
|
934
|
+
if (part) {
|
|
935
|
+
setPosition(part, ship.position.x, ship.position.y, ship.position.z);
|
|
936
|
+
setRotation(part, ship.rotation.x, ship.rotation.y, ship.rotation.z);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
} else if (ship.mesh) {
|
|
940
|
+
setPosition(ship.mesh, ship.position.x, ship.position.y, ship.position.z);
|
|
941
|
+
setRotation(ship.mesh, ship.rotation.x, ship.rotation.y, ship.rotation.z);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
function fireDesertedSpaceWeapons() {
|
|
946
|
+
try {
|
|
947
|
+
const ship = gameData.arwing;
|
|
948
|
+
|
|
949
|
+
// Simple, fast twin laser system like Deserted Space
|
|
950
|
+
const laserPositions = [
|
|
951
|
+
{ x: -1.5, y: 0, z: 2.5 },
|
|
952
|
+
{ x: 1.5, y: 0, z: 2.5 },
|
|
953
|
+
];
|
|
954
|
+
|
|
955
|
+
for (let pos of laserPositions) {
|
|
956
|
+
const worldX = ship.position.x + pos.x;
|
|
957
|
+
const worldY = ship.position.y + pos.y;
|
|
958
|
+
const worldZ = ship.position.z + pos.z;
|
|
959
|
+
|
|
960
|
+
// Create fast, bright laser projectiles
|
|
961
|
+
const laser = trackMesh(createSphere(0.2, 0x00ff00, [worldX, worldY, worldZ]));
|
|
962
|
+
setScale(laser, 1, 1, 3); // Long, thin lasers
|
|
963
|
+
|
|
964
|
+
gameData.projectiles.push({
|
|
965
|
+
mesh: laser,
|
|
966
|
+
position: { x: worldX, y: worldY, z: worldZ },
|
|
967
|
+
velocity: { x: 0, y: 0, z: -100 }, // Very fast
|
|
968
|
+
friendly: true,
|
|
969
|
+
damage: 1,
|
|
970
|
+
life: 3.0,
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// Update score for firing (tiny increment for activity)
|
|
975
|
+
gameData.score += 1;
|
|
976
|
+
} catch (e) {
|
|
977
|
+
console.warn('Weapon firing error:', e);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
function createMuzzleFlash(x, y, z) {
|
|
982
|
+
for (let i = 0; i < 8; i++) {
|
|
983
|
+
gameData.particles.push({
|
|
984
|
+
position: {
|
|
985
|
+
x: x + (Math.random() - 0.5) * 0.8,
|
|
986
|
+
y: y + (Math.random() - 0.5) * 0.8,
|
|
987
|
+
z: z + Math.random() * 1,
|
|
988
|
+
},
|
|
989
|
+
velocity: {
|
|
990
|
+
x: (Math.random() - 0.5) * 12,
|
|
991
|
+
y: (Math.random() - 0.5) * 12,
|
|
992
|
+
z: (Math.random() - 0.5) * 8,
|
|
993
|
+
},
|
|
994
|
+
life: 0.4,
|
|
995
|
+
maxLife: 0.4,
|
|
996
|
+
color: Math.random() > 0.5 ? 0x00ffaa : 0x88ffff,
|
|
997
|
+
size: Math.random() * 0.5 + 0.3,
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
function createEngineTrails() {
|
|
1003
|
+
const ship = gameData.arwing;
|
|
1004
|
+
|
|
1005
|
+
// Create boost trails from engine positions
|
|
1006
|
+
const enginePositions = [
|
|
1007
|
+
{ x: -2.2, y: -0.3, z: -2.5 },
|
|
1008
|
+
{ x: 2.2, y: -0.3, z: -2.5 },
|
|
1009
|
+
];
|
|
1010
|
+
|
|
1011
|
+
for (let pos of enginePositions) {
|
|
1012
|
+
for (let i = 0; i < 3; i++) {
|
|
1013
|
+
gameData.particles.push({
|
|
1014
|
+
position: {
|
|
1015
|
+
x: ship.position.x + pos.x + (Math.random() - 0.5) * 0.5,
|
|
1016
|
+
y: ship.position.y + pos.y + (Math.random() - 0.5) * 0.3,
|
|
1017
|
+
z: ship.position.z + pos.z,
|
|
1018
|
+
},
|
|
1019
|
+
velocity: {
|
|
1020
|
+
x: (Math.random() - 0.5) * 4,
|
|
1021
|
+
y: (Math.random() - 0.5) * 2,
|
|
1022
|
+
z: 15 + Math.random() * 10, // Trail behind ship
|
|
1023
|
+
},
|
|
1024
|
+
life: 0.8,
|
|
1025
|
+
maxLife: 0.8,
|
|
1026
|
+
color: Math.random() > 0.5 ? 0xff6600 : 0xffaa00,
|
|
1027
|
+
size: Math.random() * 0.4 + 0.2,
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// === ENEMY AI SYSTEM ===
|
|
1034
|
+
function updateEnemies(dt) {
|
|
1035
|
+
for (let i = gameData.enemies.length - 1; i >= 0; i--) {
|
|
1036
|
+
const enemy = gameData.enemies[i];
|
|
1037
|
+
|
|
1038
|
+
// Simple AI behavior
|
|
1039
|
+
updateEnemyAI(enemy, dt);
|
|
1040
|
+
|
|
1041
|
+
// Update enemy mesh position
|
|
1042
|
+
if (enemy.mesh) {
|
|
1043
|
+
setPosition(enemy.mesh, enemy.position.x, enemy.position.y, enemy.position.z);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// Update enemy wings
|
|
1047
|
+
if (enemy.wings) {
|
|
1048
|
+
setPosition(enemy.wings[0], enemy.position.x - 2, enemy.position.y, enemy.position.z);
|
|
1049
|
+
setPosition(enemy.wings[1], enemy.position.x + 2, enemy.position.y, enemy.position.z);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// Update enemy glow
|
|
1053
|
+
if (enemy.glow) {
|
|
1054
|
+
setPosition(enemy.glow, enemy.position.x, enemy.position.y, enemy.position.z - 2);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// Remove if too close or dead (changed from z > 30 to z < -30 since they move toward player)
|
|
1058
|
+
if (enemy.position.z < -30 || enemy.health <= 0) {
|
|
1059
|
+
if (enemy.position.z < -30) {
|
|
1060
|
+
// Enemy reached player
|
|
1061
|
+
gameData.health -= 20;
|
|
1062
|
+
console.log('💥 Enemy breakthrough! Health:', gameData.health);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// Cleanup enemy and all parts
|
|
1066
|
+
if (enemy.mesh) destroyMesh(enemy.mesh);
|
|
1067
|
+
if (enemy.wings) {
|
|
1068
|
+
destroyMesh(enemy.wings[0]);
|
|
1069
|
+
destroyMesh(enemy.wings[1]);
|
|
1070
|
+
}
|
|
1071
|
+
if (enemy.glow) destroyMesh(enemy.glow);
|
|
1072
|
+
gameData.enemies.splice(i, 1);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
function updateEnemyAI(enemy, dt) {
|
|
1078
|
+
// Move toward player (negative Z direction since player is at Z=0)
|
|
1079
|
+
enemy.position.z -= enemy.speed * dt;
|
|
1080
|
+
|
|
1081
|
+
if (enemy.movePattern === 'weaving') {
|
|
1082
|
+
// Spectacular weaving movement - better than Deserted Space!
|
|
1083
|
+
enemy.position.x += Math.sin(gameData.time * 3 + enemy.position.z * 0.1) * 25 * dt;
|
|
1084
|
+
enemy.position.y += Math.cos(gameData.time * 2 + enemy.position.z * 0.05) * 12 * dt;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// Occasional shooting
|
|
1088
|
+
if (gameData.time - enemy.lastShot > 2 && Math.random() > 0.98) {
|
|
1089
|
+
fireEnemyWeapon(enemy);
|
|
1090
|
+
enemy.lastShot = gameData.time;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
function fireEnemyWeapon(enemy) {
|
|
1095
|
+
try {
|
|
1096
|
+
const laser = trackMesh(
|
|
1097
|
+
createSphere(0.12, 0xff3300, [enemy.position.x, enemy.position.y, enemy.position.z + 1])
|
|
1098
|
+
);
|
|
1099
|
+
|
|
1100
|
+
gameData.projectiles.push({
|
|
1101
|
+
mesh: laser,
|
|
1102
|
+
position: { ...enemy.position },
|
|
1103
|
+
velocity: { x: 0, y: 0, z: 25 },
|
|
1104
|
+
friendly: false,
|
|
1105
|
+
damage: 15,
|
|
1106
|
+
life: 2.0,
|
|
1107
|
+
});
|
|
1108
|
+
} catch (e) {
|
|
1109
|
+
console.warn('Enemy weapon error:', e);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// === PROJECTILE SYSTEM ===
|
|
1114
|
+
function updateProjectiles(dt) {
|
|
1115
|
+
for (let i = gameData.projectiles.length - 1; i >= 0; i--) {
|
|
1116
|
+
const proj = gameData.projectiles[i];
|
|
1117
|
+
|
|
1118
|
+
// Move projectile
|
|
1119
|
+
proj.position.x += proj.velocity.x * dt;
|
|
1120
|
+
proj.position.y += proj.velocity.y * dt;
|
|
1121
|
+
proj.position.z += proj.velocity.z * dt;
|
|
1122
|
+
|
|
1123
|
+
proj.life -= dt;
|
|
1124
|
+
|
|
1125
|
+
if (proj.mesh) {
|
|
1126
|
+
setPosition(proj.mesh, proj.position.x, proj.position.y, proj.position.z);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// Collision detection
|
|
1130
|
+
if (proj.friendly) {
|
|
1131
|
+
// Player projectiles vs enemies
|
|
1132
|
+
for (let j = gameData.enemies.length - 1; j >= 0; j--) {
|
|
1133
|
+
const enemy = gameData.enemies[j];
|
|
1134
|
+
const distance = Math.sqrt(
|
|
1135
|
+
Math.pow(proj.position.x - enemy.position.x, 2) +
|
|
1136
|
+
Math.pow(proj.position.y - enemy.position.y, 2) +
|
|
1137
|
+
Math.pow(proj.position.z - enemy.position.z, 2)
|
|
1138
|
+
);
|
|
1139
|
+
|
|
1140
|
+
if (distance < 2.5) {
|
|
1141
|
+
// Hit enemy!
|
|
1142
|
+
enemy.health -= proj.damage;
|
|
1143
|
+
|
|
1144
|
+
// Score and effects
|
|
1145
|
+
gameData.score += 50;
|
|
1146
|
+
createHitEffect(enemy.position.x, enemy.position.y, enemy.position.z);
|
|
1147
|
+
|
|
1148
|
+
// Remove projectile
|
|
1149
|
+
if (proj.mesh) destroyMesh(proj.mesh);
|
|
1150
|
+
gameData.projectiles.splice(i, 1);
|
|
1151
|
+
|
|
1152
|
+
// Check if enemy destroyed
|
|
1153
|
+
if (enemy.health <= 0) {
|
|
1154
|
+
gameData.score +=
|
|
1155
|
+
enemy.type === 'bomber' ? 300 : enemy.type === 'interceptor' ? 150 : 200;
|
|
1156
|
+
gameData.kills++;
|
|
1157
|
+
createExplosion(enemy.position.x, enemy.position.y, enemy.position.z);
|
|
1158
|
+
console.log(
|
|
1159
|
+
`💥 ${enemy.type} destroyed! +${enemy.type === 'bomber' ? 300 : enemy.type === 'interceptor' ? 150 : 200} points`
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
break;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
} else {
|
|
1167
|
+
// Enemy projectiles vs player
|
|
1168
|
+
const player = gameData.arwing;
|
|
1169
|
+
const distance = Math.sqrt(
|
|
1170
|
+
Math.pow(proj.position.x - player.position.x, 2) +
|
|
1171
|
+
Math.pow(proj.position.y - player.position.y, 2) +
|
|
1172
|
+
Math.pow(proj.position.z - player.position.z, 2)
|
|
1173
|
+
);
|
|
1174
|
+
|
|
1175
|
+
if (distance < 2.0) {
|
|
1176
|
+
// Player hit!
|
|
1177
|
+
gameData.health -= proj.damage;
|
|
1178
|
+
gameData.shields = Math.max(0, gameData.shields - proj.damage);
|
|
1179
|
+
|
|
1180
|
+
createHitEffect(player.position.x, player.position.y, player.position.z);
|
|
1181
|
+
|
|
1182
|
+
// Remove projectile
|
|
1183
|
+
if (proj.mesh) destroyMesh(proj.mesh);
|
|
1184
|
+
gameData.projectiles.splice(i, 1);
|
|
1185
|
+
|
|
1186
|
+
console.log(`💥 Player hit! Health: ${gameData.health}, Shields: ${gameData.shields}`);
|
|
1187
|
+
continue;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// Remove if out of range or lifetime expired
|
|
1192
|
+
if (proj.life <= 0 || Math.abs(proj.position.z) > 100) {
|
|
1193
|
+
if (proj.mesh) destroyMesh(proj.mesh);
|
|
1194
|
+
gameData.projectiles.splice(i, 1);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
function createHitEffect(x, y, z) {
|
|
1200
|
+
for (let i = 0; i < 10; i++) {
|
|
1201
|
+
gameData.particles.push({
|
|
1202
|
+
position: {
|
|
1203
|
+
x: x + (Math.random() - 0.5) * 2,
|
|
1204
|
+
y: y + (Math.random() - 0.5) * 2,
|
|
1205
|
+
z: z + (Math.random() - 0.5) * 2,
|
|
1206
|
+
},
|
|
1207
|
+
velocity: {
|
|
1208
|
+
x: (Math.random() - 0.5) * 15,
|
|
1209
|
+
y: (Math.random() - 0.5) * 15,
|
|
1210
|
+
z: (Math.random() - 0.5) * 10,
|
|
1211
|
+
},
|
|
1212
|
+
life: 0.8,
|
|
1213
|
+
maxLife: 0.8,
|
|
1214
|
+
color: 0xff6600,
|
|
1215
|
+
size: Math.random() * 0.4 + 0.3,
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
function createExplosion(x, y, z) {
|
|
1221
|
+
for (let i = 0; i < 25; i++) {
|
|
1222
|
+
gameData.particles.push({
|
|
1223
|
+
position: {
|
|
1224
|
+
x: x + (Math.random() - 0.5) * 3,
|
|
1225
|
+
y: y + (Math.random() - 0.5) * 3,
|
|
1226
|
+
z: z + (Math.random() - 0.5) * 3,
|
|
1227
|
+
},
|
|
1228
|
+
velocity: {
|
|
1229
|
+
x: (Math.random() - 0.5) * 20,
|
|
1230
|
+
y: (Math.random() - 0.5) * 20,
|
|
1231
|
+
z: (Math.random() - 0.5) * 15,
|
|
1232
|
+
},
|
|
1233
|
+
life: 1.2,
|
|
1234
|
+
maxLife: 1.2,
|
|
1235
|
+
color: Math.random() > 0.5 ? 0xff3300 : 0xff8800,
|
|
1236
|
+
size: Math.random() * 0.6 + 0.4,
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// === PARTICLE SYSTEM ===
|
|
1242
|
+
function updateParticles(dt) {
|
|
1243
|
+
for (let i = gameData.particles.length - 1; i >= 0; i--) {
|
|
1244
|
+
const p = gameData.particles[i];
|
|
1245
|
+
|
|
1246
|
+
// Update physics
|
|
1247
|
+
p.position.x += p.velocity.x * dt;
|
|
1248
|
+
p.position.y += p.velocity.y * dt;
|
|
1249
|
+
p.position.z += p.velocity.z * dt;
|
|
1250
|
+
|
|
1251
|
+
// Apply gravity and drag
|
|
1252
|
+
p.velocity.y -= 8 * dt;
|
|
1253
|
+
p.velocity.x *= 0.95;
|
|
1254
|
+
p.velocity.y *= 0.95;
|
|
1255
|
+
p.velocity.z *= 0.95;
|
|
1256
|
+
|
|
1257
|
+
// Age particle
|
|
1258
|
+
p.life -= dt;
|
|
1259
|
+
|
|
1260
|
+
if (p.life <= 0) {
|
|
1261
|
+
gameData.particles.splice(i, 1);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
// updateStarfield duplicate removed - already defined above at line 385
|
|
1267
|
+
|
|
1268
|
+
function updateSpaceFloor(dt) {
|
|
1269
|
+
// Simple, stable starfield update - no more crashes!
|
|
1270
|
+
updateStarfield(dt);
|
|
1271
|
+
|
|
1272
|
+
// Animate all space elements with Crystal Cathedral effects!
|
|
1273
|
+
for (let element of gameData.spaceFloor) {
|
|
1274
|
+
if (element.mesh) {
|
|
1275
|
+
// Move toward player
|
|
1276
|
+
element.customZ += scrollSpeed * dt;
|
|
1277
|
+
|
|
1278
|
+
let y = -58; // Default height
|
|
1279
|
+
let x = element.x;
|
|
1280
|
+
|
|
1281
|
+
// SPECTACULAR animations for different element types
|
|
1282
|
+
if (element.type === 'pillar') {
|
|
1283
|
+
// Crystal pillars pulse with holographic energy
|
|
1284
|
+
element.glowPhase += dt * 2;
|
|
1285
|
+
const glow = Math.sin(element.glowPhase) * 0.5 + 0.5;
|
|
1286
|
+
y = element.height / 2 - 60 + Math.sin(element.glowPhase * 0.5) * 2;
|
|
1287
|
+
|
|
1288
|
+
// Update pillar cap if it exists
|
|
1289
|
+
if (element.cap) {
|
|
1290
|
+
setPosition(
|
|
1291
|
+
element.cap,
|
|
1292
|
+
x,
|
|
1293
|
+
element.height - 40 + Math.sin(element.glowPhase) * 3,
|
|
1294
|
+
element.customZ
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
} else if (element.type === 'crystal') {
|
|
1298
|
+
// Floating crystals with complex motion
|
|
1299
|
+
element.floatPhase += dt * 1.5;
|
|
1300
|
+
y = element.originalY + Math.sin(element.floatPhase) * 8;
|
|
1301
|
+
x = element.x + Math.cos(element.floatPhase * 0.7) * 5;
|
|
1302
|
+
} else if (element.type === 'horizontal' || element.type === 'vertical') {
|
|
1303
|
+
// Holographic grid lines pulse
|
|
1304
|
+
element.glowPhase += dt * 4;
|
|
1305
|
+
const pulseIntensity = Math.sin(element.glowPhase) * 0.3 + 0.7;
|
|
1306
|
+
y = -55 + Math.sin(element.glowPhase) * 2;
|
|
1307
|
+
|
|
1308
|
+
// Change color cycling
|
|
1309
|
+
const intensity = Math.sin(element.glowPhase * 2) * 0.5 + 0.5;
|
|
1310
|
+
// Note: We can't easily change mesh colors in real-time, but the bobbing looks great
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
setPosition(element.mesh, x, y, element.customZ);
|
|
1314
|
+
|
|
1315
|
+
// Reset when element passes the player
|
|
1316
|
+
if (element.customZ > 100) {
|
|
1317
|
+
element.customZ = element.originalZ - 200;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// Animate atmospheric particles like Crystal Cathedral
|
|
1323
|
+
if (gameData.atmosphericParticles) {
|
|
1324
|
+
for (let particle of gameData.atmosphericParticles) {
|
|
1325
|
+
if (particle.mesh) {
|
|
1326
|
+
// Move particles toward player
|
|
1327
|
+
particle.customZ += particle.speed * dt;
|
|
1328
|
+
|
|
1329
|
+
// Floating motion like Crystal Cathedral
|
|
1330
|
+
particle.phase += dt * 2;
|
|
1331
|
+
const floatY = particle.originalY + Math.sin(particle.phase) * 6;
|
|
1332
|
+
const driftX = particle.x + Math.cos(particle.phase * 0.5) * 4;
|
|
1333
|
+
|
|
1334
|
+
setPosition(particle.mesh, driftX, floatY, particle.customZ);
|
|
1335
|
+
|
|
1336
|
+
// Reset when too close
|
|
1337
|
+
if (particle.customZ > 40) {
|
|
1338
|
+
particle.customZ = particle.originalZ - 200;
|
|
1339
|
+
particle.x = (Math.random() - 0.5) * 300;
|
|
1340
|
+
particle.originalY = (Math.random() - 0.5) * 80;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
function updateCamera(dt) {
|
|
1348
|
+
// Smooth camera follow
|
|
1349
|
+
const target = gameData.arwing.position;
|
|
1350
|
+
const cam = gameData.camera;
|
|
1351
|
+
|
|
1352
|
+
const targetX = target.x * 0.4;
|
|
1353
|
+
const targetY = target.y * 0.3 + 5;
|
|
1354
|
+
const targetZ = target.z + 18;
|
|
1355
|
+
|
|
1356
|
+
cam.position.x += (targetX - cam.position.x) * cam.followSmoothing;
|
|
1357
|
+
cam.position.y += (targetY - cam.position.y) * cam.followSmoothing;
|
|
1358
|
+
cam.position.z += (targetZ - cam.position.z) * cam.followSmoothing;
|
|
1359
|
+
|
|
1360
|
+
// Camera shake
|
|
1361
|
+
if (cam.shake > 0) {
|
|
1362
|
+
cam.shake -= dt * 3;
|
|
1363
|
+
const intensity = cam.shakeIntensity * cam.shake;
|
|
1364
|
+
|
|
1365
|
+
const shakeX = (Math.random() - 0.5) * intensity;
|
|
1366
|
+
const shakeY = (Math.random() - 0.5) * intensity;
|
|
1367
|
+
const shakeZ = (Math.random() - 0.5) * intensity * 0.5;
|
|
1368
|
+
|
|
1369
|
+
if (typeof setCameraPosition === 'function') {
|
|
1370
|
+
setCameraPosition(cam.position.x + shakeX, cam.position.y + shakeY, cam.position.z + shakeZ);
|
|
1371
|
+
}
|
|
1372
|
+
} else {
|
|
1373
|
+
if (typeof setCameraPosition === 'function') {
|
|
1374
|
+
setCameraPosition(cam.position.x, cam.position.y, cam.position.z);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
function updateEffects(dt) {
|
|
1380
|
+
// Screen flash effect
|
|
1381
|
+
if (gameData.effects.screenFlash > 0) {
|
|
1382
|
+
gameData.effects.screenFlash -= dt * 4;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
// Energy regeneration
|
|
1386
|
+
if (gameData.energy < 100) {
|
|
1387
|
+
gameData.energy = Math.min(100, gameData.energy + 20 * dt);
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// Shield regeneration (if not taking damage)
|
|
1391
|
+
if (gameData.shields < 100 && gameData.time - (gameData.lastDamageTime || 0) > 3) {
|
|
1392
|
+
gameData.shields = Math.min(100, gameData.shields + 15 * dt);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
function updateTargetingSystem() {
|
|
1397
|
+
// Update targeting based on mouse position
|
|
1398
|
+
// This would be used for more advanced targeting mechanics
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
// === DRAWING FUNCTIONS ===
|
|
1402
|
+
function drawGameEnvironment() {
|
|
1403
|
+
// Deep space background with slight blue tint
|
|
1404
|
+
cls(0x000015);
|
|
1405
|
+
|
|
1406
|
+
// Advanced starfield
|
|
1407
|
+
drawAdvancedStarfield();
|
|
1408
|
+
|
|
1409
|
+
// 3D space floor and objects render automatically
|
|
1410
|
+
|
|
1411
|
+
// Particle effects overlay
|
|
1412
|
+
drawParticleEffects();
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
function drawParticleEffects() {
|
|
1416
|
+
for (let p of gameData.particles) {
|
|
1417
|
+
if (p.position.z > -50 && p.position.z < 50) {
|
|
1418
|
+
// 3D to 2D projection
|
|
1419
|
+
const depth = Math.abs(p.position.z) + 10;
|
|
1420
|
+
const screenX = 320 + (p.position.x / depth) * 600;
|
|
1421
|
+
const screenY = 180 + (p.position.y / depth) * 600;
|
|
1422
|
+
|
|
1423
|
+
if (screenX > -20 && screenX < 660 && screenY > -20 && screenY < 380) {
|
|
1424
|
+
const alpha = Math.floor(255 * (p.life / p.maxLife));
|
|
1425
|
+
const size = p.size * (30 / depth);
|
|
1426
|
+
|
|
1427
|
+
rect(
|
|
1428
|
+
screenX - size / 2,
|
|
1429
|
+
screenY - size / 2,
|
|
1430
|
+
size,
|
|
1431
|
+
size,
|
|
1432
|
+
rgba8((p.color >> 16) & 0xff, (p.color >> 8) & 0xff, p.color & 0xff, alpha),
|
|
1433
|
+
true
|
|
1434
|
+
);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
function drawProfessionalHUD() {
|
|
1441
|
+
try {
|
|
1442
|
+
// Deserted Space style HUD - clean and minimal
|
|
1443
|
+
|
|
1444
|
+
// Large score display (top center)
|
|
1445
|
+
drawDesertedSpaceScore();
|
|
1446
|
+
|
|
1447
|
+
// Health indicator (bottom left)
|
|
1448
|
+
drawDesertedSpaceHealth();
|
|
1449
|
+
|
|
1450
|
+
// Simple crosshair (center)
|
|
1451
|
+
drawDesertedSpaceCrosshair();
|
|
1452
|
+
|
|
1453
|
+
// Speed/status indicators (top corners)
|
|
1454
|
+
drawDesertedSpaceStatus();
|
|
1455
|
+
|
|
1456
|
+
// Enemy count (top right)
|
|
1457
|
+
drawDesertedSpaceEnemyCount();
|
|
1458
|
+
} catch (e) {
|
|
1459
|
+
console.warn('HUD draw error:', e);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
function drawDesertedSpaceScore() {
|
|
1464
|
+
// Large, prominent score like Deserted Space
|
|
1465
|
+
const scoreText = gameData.score.toString().padStart(8, '0');
|
|
1466
|
+
const scorePulse = Math.sin(gameData.time * 2) * 0.2 + 0.8;
|
|
1467
|
+
|
|
1468
|
+
// Background for score
|
|
1469
|
+
rect(250, 10, 140, 40, rgba8(0, 0, 0, 100), true);
|
|
1470
|
+
rect(250, 10, 140, 40, rgba8(0, 255, 100, 150), false);
|
|
1471
|
+
|
|
1472
|
+
// Score text - large and bright
|
|
1473
|
+
print('SCORE', 260, 18, rgba8(255, 255, 255, 200));
|
|
1474
|
+
print(scoreText, 260, 32, rgba8(0, 255, 0, Math.floor(255 * scorePulse)));
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
function drawDesertedSpaceHealth() {
|
|
1478
|
+
// Simple health bar like Deserted Space
|
|
1479
|
+
const healthWidth = (gameData.health / 100) * 120;
|
|
1480
|
+
|
|
1481
|
+
rect(20, 320, 130, 25, rgba8(20, 20, 20, 180), true);
|
|
1482
|
+
rect(20, 320, 130, 25, rgba8(100, 100, 100, 200), false);
|
|
1483
|
+
|
|
1484
|
+
// Health bar color
|
|
1485
|
+
let healthColor = rgba8(0, 255, 0, 200);
|
|
1486
|
+
if (gameData.health < 50) healthColor = rgba8(255, 255, 0, 200);
|
|
1487
|
+
if (gameData.health < 25) healthColor = rgba8(255, 0, 0, 200);
|
|
1488
|
+
|
|
1489
|
+
rect(25, 325, healthWidth, 15, healthColor, true);
|
|
1490
|
+
print('HULL', 30, 328, rgba8(255, 255, 255, 255));
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
function drawDesertedSpaceCrosshair() {
|
|
1494
|
+
// Simple, clean crosshair
|
|
1495
|
+
const centerX = 320,
|
|
1496
|
+
centerY = 180;
|
|
1497
|
+
const crosshairColor = rgba8(0, 255, 0, 150);
|
|
1498
|
+
|
|
1499
|
+
// Main cross
|
|
1500
|
+
line(centerX - 20, centerY, centerX + 20, centerY, crosshairColor);
|
|
1501
|
+
line(centerX, centerY - 20, centerX, centerY + 20, crosshairColor);
|
|
1502
|
+
|
|
1503
|
+
// Center dot
|
|
1504
|
+
rect(centerX - 2, centerY - 2, 4, 4, rgba8(255, 255, 255, 255), true);
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
function drawDesertedSpaceStatus() {
|
|
1508
|
+
// Top left - Wave info
|
|
1509
|
+
print(`WAVE ${gameData.wave}`, 20, 20, rgba8(255, 255, 255, 200));
|
|
1510
|
+
print(`ENEMIES: ${gameData.enemies.length}`, 20, 35, rgba8(255, 200, 0, 200));
|
|
1511
|
+
|
|
1512
|
+
// Top right - Speed
|
|
1513
|
+
const speed = Math.floor(gameData.arwing.speed || 25);
|
|
1514
|
+
print(`SPEED: ${speed}`, 500, 20, rgba8(0, 200, 255, 200));
|
|
1515
|
+
|
|
1516
|
+
if (gameData.arwing.boost) {
|
|
1517
|
+
print('BOOST', 500, 35, rgba8(255, 100, 0, 255));
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
function drawDesertedSpaceEnemyCount() {
|
|
1522
|
+
// Enemy radar dots - simple version
|
|
1523
|
+
const radarX = 580,
|
|
1524
|
+
radarY = 80;
|
|
1525
|
+
const radarSize = 40;
|
|
1526
|
+
|
|
1527
|
+
// Radar background
|
|
1528
|
+
rect(
|
|
1529
|
+
radarX - radarSize / 2,
|
|
1530
|
+
radarY - radarSize / 2,
|
|
1531
|
+
radarSize,
|
|
1532
|
+
radarSize,
|
|
1533
|
+
rgba8(0, 20, 0, 100),
|
|
1534
|
+
true
|
|
1535
|
+
);
|
|
1536
|
+
rect(
|
|
1537
|
+
radarX - radarSize / 2,
|
|
1538
|
+
radarY - radarSize / 2,
|
|
1539
|
+
radarSize,
|
|
1540
|
+
radarSize,
|
|
1541
|
+
rgba8(0, 255, 0, 150),
|
|
1542
|
+
false
|
|
1543
|
+
);
|
|
1544
|
+
|
|
1545
|
+
// Player center
|
|
1546
|
+
rect(radarX - 1, radarY - 1, 2, 2, rgba8(0, 255, 255, 255), true);
|
|
1547
|
+
|
|
1548
|
+
// Enemy dots
|
|
1549
|
+
for (let i = 0; i < Math.min(gameData.enemies.length, 8); i++) {
|
|
1550
|
+
const angle = (i / 8) * Math.PI * 2;
|
|
1551
|
+
const dotX = radarX + Math.cos(angle) * 15;
|
|
1552
|
+
const dotY = radarY + Math.sin(angle) * 15;
|
|
1553
|
+
rect(dotX - 1, dotY - 1, 2, 2, rgba8(255, 0, 0, 200), true);
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
function drawHealthPanel() {
|
|
1558
|
+
// Enhanced health bar with prominent styling
|
|
1559
|
+
rect(15, 15, 180, 35, rgba8(20, 20, 20, 220), true);
|
|
1560
|
+
rect(15, 15, 180, 35, rgba8(100, 100, 100, 255), false);
|
|
1561
|
+
rect(17, 17, 176, 31, rgba8(50, 50, 50, 150), false);
|
|
1562
|
+
|
|
1563
|
+
// Health bar with glow effect
|
|
1564
|
+
const healthWidth = (gameData.health / 100) * 170;
|
|
1565
|
+
let healthColor =
|
|
1566
|
+
gameData.health > 60
|
|
1567
|
+
? rgba8(0, 255, 0, 220)
|
|
1568
|
+
: gameData.health > 30
|
|
1569
|
+
? rgba8(255, 255, 0, 220)
|
|
1570
|
+
: rgba8(255, 0, 0, 220);
|
|
1571
|
+
|
|
1572
|
+
// Add pulsing effect for low health
|
|
1573
|
+
if (gameData.health < 30) {
|
|
1574
|
+
const pulse = Math.sin(gameData.time * 8) * 0.3 + 0.7;
|
|
1575
|
+
healthColor = rgba8(255, Math.floor(100 * pulse), 0, 220);
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
rect(20, 20, healthWidth, 25, healthColor, true);
|
|
1579
|
+
|
|
1580
|
+
// Health text
|
|
1581
|
+
print('HULL INTEGRITY', 25, 23, rgba8(255, 255, 255, 255));
|
|
1582
|
+
print(`${Math.floor(gameData.health)}%`, 150, 35, rgba8(255, 255, 255, 255));
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
function drawShieldsPanel() {
|
|
1586
|
+
// Enhanced shields bar
|
|
1587
|
+
rect(15, 55, 180, 30, rgba8(20, 20, 20, 220), true);
|
|
1588
|
+
rect(15, 55, 180, 30, rgba8(100, 100, 100, 255), false);
|
|
1589
|
+
|
|
1590
|
+
const shieldWidth = (gameData.shields / 100) * 170;
|
|
1591
|
+
|
|
1592
|
+
// Shield color with energy effect
|
|
1593
|
+
const shieldPulse = Math.sin(gameData.time * 6) * 0.2 + 0.8;
|
|
1594
|
+
const shieldColor = rgba8(0, Math.floor(150 * shieldPulse), 255, 200);
|
|
1595
|
+
|
|
1596
|
+
rect(20, 60, shieldWidth, 20, shieldColor, true);
|
|
1597
|
+
|
|
1598
|
+
// Shield text
|
|
1599
|
+
print('SHIELD POWER', 25, 63, rgba8(255, 255, 255, 255));
|
|
1600
|
+
print(`${Math.floor(gameData.shields)}%`, 150, 75, rgba8(255, 255, 255, 255));
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
function drawEnergyPanel() {
|
|
1604
|
+
// Enhanced energy bar
|
|
1605
|
+
rect(15, 90, 180, 30, rgba8(20, 20, 20, 220), true);
|
|
1606
|
+
rect(15, 90, 180, 30, rgba8(100, 100, 100, 255), false);
|
|
1607
|
+
|
|
1608
|
+
const energyWidth = (gameData.energy / 100) * 170;
|
|
1609
|
+
|
|
1610
|
+
// Energy color with charging effect
|
|
1611
|
+
const energyPulse = Math.sin(gameData.time * 5) * 0.3 + 0.7;
|
|
1612
|
+
const energyColor = rgba8(255, Math.floor(150 * energyPulse), 0, 200);
|
|
1613
|
+
|
|
1614
|
+
rect(20, 95, energyWidth, 20, energyColor, true);
|
|
1615
|
+
|
|
1616
|
+
print('WEAPON ENERGY', 25, 98, rgba8(255, 255, 255, 255));
|
|
1617
|
+
print(`${Math.floor(gameData.energy)}%`, 150, 110, rgba8(255, 255, 255, 255));
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
function drawScorePanel() {
|
|
1621
|
+
// Large, prominent score display like Deserted Space
|
|
1622
|
+
rect(450, 15, 180, 80, rgba8(10, 10, 30, 200), true);
|
|
1623
|
+
rect(450, 15, 180, 80, rgba8(0, 255, 255, 150), false);
|
|
1624
|
+
rect(452, 17, 176, 76, rgba8(0, 150, 255, 80), false);
|
|
1625
|
+
|
|
1626
|
+
// Big, bright score text
|
|
1627
|
+
print('SCORE', 460, 25, rgba8(0, 255, 255, 255));
|
|
1628
|
+
|
|
1629
|
+
// Animate score display
|
|
1630
|
+
const scorePulse = Math.sin(gameData.time * 4) * 0.2 + 0.8;
|
|
1631
|
+
const scoreColor = rgba8(255, Math.floor(255 * scorePulse), 0, 255);
|
|
1632
|
+
print(gameData.score.toString(), 460, 45, scoreColor);
|
|
1633
|
+
|
|
1634
|
+
print(`WAVE ${gameData.wave}`, 460, 65, rgba8(150, 255, 150, 255));
|
|
1635
|
+
print(`KILLS: ${gameData.kills}`, 460, 80, rgba8(255, 200, 100, 255));
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
function drawStatusPanel() {
|
|
1639
|
+
// Enhanced status indicators
|
|
1640
|
+
let y = 105;
|
|
1641
|
+
|
|
1642
|
+
// Speed indicator
|
|
1643
|
+
const speed = Math.floor(gameData.arwing.speed || 15);
|
|
1644
|
+
print(`SPEED: ${speed}`, 460, y, rgba8(0, 200, 255, 255));
|
|
1645
|
+
y += 15;
|
|
1646
|
+
|
|
1647
|
+
if (gameData.arwing.boost) {
|
|
1648
|
+
const boostPulse = Math.sin(gameData.time * 10) * 0.5 + 0.5;
|
|
1649
|
+
print('>>> BOOST ACTIVE <<<', 460, y, rgba8(255, Math.floor(200 * boostPulse), 0, 255));
|
|
1650
|
+
y += 15;
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
if (gameData.shields < 25) {
|
|
1654
|
+
const alertPulse = Math.sin(gameData.time * 8) * 0.5 + 0.5;
|
|
1655
|
+
print('!!! LOW SHIELDS !!!', 460, y, rgba8(255, Math.floor(100 * alertPulse), 0, 255));
|
|
1656
|
+
y += 15;
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
if (gameData.energy < 20) {
|
|
1660
|
+
print('LOW WEAPON ENERGY', 460, y, rgba8(255, 255, 0, 255));
|
|
1661
|
+
y += 15;
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
// Combat status
|
|
1665
|
+
if (gameData.enemies.length > 0) {
|
|
1666
|
+
print(`TARGETS: ${gameData.enemies.length}`, 460, y, rgba8(255, 100, 100, 255));
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
function drawTargetingHUD() {
|
|
1671
|
+
// Large, prominent central crosshair
|
|
1672
|
+
const centerX = 320,
|
|
1673
|
+
centerY = 180;
|
|
1674
|
+
|
|
1675
|
+
// Dynamic crosshair with bright colors
|
|
1676
|
+
const pulse = Math.sin(gameData.time * 8) * 0.4 + 0.6;
|
|
1677
|
+
const crosshairColor = rgba8(0, 255, 100, Math.floor(pulse * 255));
|
|
1678
|
+
|
|
1679
|
+
// Main crosshair - larger and more visible
|
|
1680
|
+
line(centerX - 25, centerY, centerX + 25, centerY, crosshairColor);
|
|
1681
|
+
line(centerX, centerY - 25, centerX, centerY + 25, crosshairColor);
|
|
1682
|
+
|
|
1683
|
+
// Inner crosshair
|
|
1684
|
+
line(centerX - 10, centerY, centerX + 10, centerY, rgba8(255, 255, 255, 200));
|
|
1685
|
+
line(centerX, centerY - 10, centerX, centerY + 10, rgba8(255, 255, 255, 200));
|
|
1686
|
+
|
|
1687
|
+
// Animated targeting brackets
|
|
1688
|
+
const bracketSize = 12;
|
|
1689
|
+
const bracketOffset = Math.sin(gameData.time * 6) * 3 + 30;
|
|
1690
|
+
const bracketColor = rgba8(0, 255, 255, Math.floor(pulse * 255));
|
|
1691
|
+
|
|
1692
|
+
// Top-left bracket
|
|
1693
|
+
line(
|
|
1694
|
+
centerX - bracketOffset,
|
|
1695
|
+
centerY - bracketOffset,
|
|
1696
|
+
centerX - bracketOffset + bracketSize,
|
|
1697
|
+
centerY - bracketOffset,
|
|
1698
|
+
bracketColor
|
|
1699
|
+
);
|
|
1700
|
+
line(
|
|
1701
|
+
centerX - bracketOffset,
|
|
1702
|
+
centerY - bracketOffset,
|
|
1703
|
+
centerX - bracketOffset,
|
|
1704
|
+
centerY - bracketOffset + bracketSize,
|
|
1705
|
+
bracketColor
|
|
1706
|
+
);
|
|
1707
|
+
|
|
1708
|
+
// Top-right bracket
|
|
1709
|
+
line(
|
|
1710
|
+
centerX + bracketOffset,
|
|
1711
|
+
centerY - bracketOffset,
|
|
1712
|
+
centerX + bracketOffset - bracketSize,
|
|
1713
|
+
centerY - bracketOffset,
|
|
1714
|
+
bracketColor
|
|
1715
|
+
);
|
|
1716
|
+
line(
|
|
1717
|
+
centerX + bracketOffset,
|
|
1718
|
+
centerY - bracketOffset,
|
|
1719
|
+
centerX + bracketOffset,
|
|
1720
|
+
centerY - bracketOffset + bracketSize,
|
|
1721
|
+
bracketColor
|
|
1722
|
+
);
|
|
1723
|
+
|
|
1724
|
+
// Bottom-left bracket
|
|
1725
|
+
line(
|
|
1726
|
+
centerX - bracketOffset,
|
|
1727
|
+
centerY + bracketOffset,
|
|
1728
|
+
centerX - bracketOffset + bracketSize,
|
|
1729
|
+
centerY + bracketOffset,
|
|
1730
|
+
bracketColor
|
|
1731
|
+
);
|
|
1732
|
+
line(
|
|
1733
|
+
centerX - bracketOffset,
|
|
1734
|
+
centerY + bracketOffset,
|
|
1735
|
+
centerX - bracketOffset,
|
|
1736
|
+
centerY + bracketOffset - bracketSize,
|
|
1737
|
+
bracketColor
|
|
1738
|
+
);
|
|
1739
|
+
|
|
1740
|
+
// Bottom-right bracket
|
|
1741
|
+
line(
|
|
1742
|
+
centerX + bracketOffset,
|
|
1743
|
+
centerY + bracketOffset,
|
|
1744
|
+
centerX + bracketOffset - bracketSize,
|
|
1745
|
+
centerY + bracketOffset,
|
|
1746
|
+
bracketColor
|
|
1747
|
+
);
|
|
1748
|
+
line(
|
|
1749
|
+
centerX + bracketOffset,
|
|
1750
|
+
centerY + bracketOffset,
|
|
1751
|
+
centerX + bracketOffset,
|
|
1752
|
+
centerY + bracketOffset - bracketSize,
|
|
1753
|
+
bracketColor
|
|
1754
|
+
);
|
|
1755
|
+
|
|
1756
|
+
// Weapon ready indicator
|
|
1757
|
+
if (gameData.energy > 10) {
|
|
1758
|
+
print('WEAPONS READY', 270, 150, rgba8(0, 255, 0, Math.floor(pulse * 255)));
|
|
1759
|
+
} else {
|
|
1760
|
+
print('RECHARGING...', 275, 150, rgba8(255, 100, 0, 255));
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
function drawAdvancedRadar() {
|
|
1765
|
+
const radarX = 550,
|
|
1766
|
+
radarY = 280;
|
|
1767
|
+
const radarSize = 60;
|
|
1768
|
+
|
|
1769
|
+
// Radar background
|
|
1770
|
+
rect(
|
|
1771
|
+
radarX - radarSize / 2,
|
|
1772
|
+
radarY - radarSize / 2,
|
|
1773
|
+
radarSize,
|
|
1774
|
+
radarSize,
|
|
1775
|
+
rgba8(0, 50, 0, 150),
|
|
1776
|
+
true
|
|
1777
|
+
);
|
|
1778
|
+
rect(
|
|
1779
|
+
radarX - radarSize / 2,
|
|
1780
|
+
radarY - radarSize / 2,
|
|
1781
|
+
radarSize,
|
|
1782
|
+
radarSize,
|
|
1783
|
+
rgba8(0, 255, 0, 255),
|
|
1784
|
+
false
|
|
1785
|
+
);
|
|
1786
|
+
|
|
1787
|
+
// Grid lines
|
|
1788
|
+
line(radarX - radarSize / 2, radarY, radarX + radarSize / 2, radarY, rgba8(0, 150, 0, 100));
|
|
1789
|
+
line(radarX, radarY - radarSize / 2, radarX, radarY + radarSize / 2, rgba8(0, 150, 0, 100));
|
|
1790
|
+
|
|
1791
|
+
// Player dot (center)
|
|
1792
|
+
rect(radarX - 2, radarY - 2, 4, 4, rgba8(0, 255, 255, 255), true);
|
|
1793
|
+
|
|
1794
|
+
// Enemy dots
|
|
1795
|
+
for (let enemy of gameData.enemies) {
|
|
1796
|
+
const relX = (enemy.position.x / 50) * (radarSize / 2);
|
|
1797
|
+
const relZ = (enemy.position.z / 100) * (radarSize / 2);
|
|
1798
|
+
const dotX = radarX + relX;
|
|
1799
|
+
const dotY = radarY + relZ;
|
|
1800
|
+
|
|
1801
|
+
if (Math.abs(relX) < radarSize / 2 && Math.abs(relZ) < radarSize / 2) {
|
|
1802
|
+
const color = enemy.type === 'bomber' ? rgba8(255, 100, 0, 255) : rgba8(255, 0, 0, 255);
|
|
1803
|
+
rect(dotX - 1, dotY - 1, 3, 3, color, true);
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
function drawGameOverScreen() {
|
|
1809
|
+
cls(0x000011);
|
|
1810
|
+
|
|
1811
|
+
// Game over text
|
|
1812
|
+
print('MISSION FAILED', 240, 100, rgba8(255, 0, 0, 255));
|
|
1813
|
+
print(`FINAL SCORE: ${gameData.score}`, 220, 130, rgba8(255, 255, 0, 255));
|
|
1814
|
+
print(`WAVES COMPLETED: ${gameData.wave - 1}`, 200, 160, rgba8(0, 255, 255, 255));
|
|
1815
|
+
print(`ENEMIES DESTROYED: ${gameData.kills}`, 180, 190, rgba8(150, 255, 150, 255));
|
|
1816
|
+
|
|
1817
|
+
print('PRESS SPACE TO RESTART', 210, 240, rgba8(255, 255, 255, 255));
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
// Initialize the game
|
|
1821
|
+
console.log('🚀 PROFESSIONAL STAR FOX NOVA 64 - Loading...');
|