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,1383 @@
|
|
|
1
|
+
// CYBERPUNK CITY 3D - Ultimate Nintendo 64 Style 3D City
|
|
2
|
+
// Full GPU-accelerated 3D world with dynamic lighting, flying vehicles, and retro effects
|
|
3
|
+
|
|
4
|
+
let gameTime = 0;
|
|
5
|
+
let cityObjects = [];
|
|
6
|
+
let vehicles = [];
|
|
7
|
+
let particles = [];
|
|
8
|
+
let buildings = [];
|
|
9
|
+
let player = null;
|
|
10
|
+
let camera = { x: 0, y: 20, z: 30, targetX: 0, targetY: 10, targetZ: 0 };
|
|
11
|
+
let cityLights = [];
|
|
12
|
+
let neonSigns = [];
|
|
13
|
+
let flying = false;
|
|
14
|
+
let speed = 0;
|
|
15
|
+
|
|
16
|
+
// Screen management
|
|
17
|
+
let gameState = 'start'; // 'start', 'exploring'
|
|
18
|
+
let startScreenTime = 0;
|
|
19
|
+
let uiButtons = [];
|
|
20
|
+
|
|
21
|
+
let dataPackets = [];
|
|
22
|
+
let playerScore = 0;
|
|
23
|
+
|
|
24
|
+
// Mission system
|
|
25
|
+
let currentMission = null;
|
|
26
|
+
let missionIndex = 0;
|
|
27
|
+
let missionTimer = 0;
|
|
28
|
+
let missionMsg = '';
|
|
29
|
+
let missionMsgTimer = 0;
|
|
30
|
+
|
|
31
|
+
// Security drones
|
|
32
|
+
let drones = [];
|
|
33
|
+
let droneProjectiles = [];
|
|
34
|
+
|
|
35
|
+
// Player combat
|
|
36
|
+
let playerHealth = 100;
|
|
37
|
+
let playerMaxHealth = 100;
|
|
38
|
+
let playerDmgCD = 0;
|
|
39
|
+
let dronesDestroyed = 0;
|
|
40
|
+
let shake = null;
|
|
41
|
+
let checkpointMesh = null;
|
|
42
|
+
let checkpointGlow = null;
|
|
43
|
+
|
|
44
|
+
function spawnPackets() {
|
|
45
|
+
for (let i = 0; i < 20; i++) {
|
|
46
|
+
const x = (Math.random() - 0.5) * CITY_SIZE * 1.5;
|
|
47
|
+
const y = 3 + Math.random() * 20;
|
|
48
|
+
const z = (Math.random() - 0.5) * CITY_SIZE * 1.5;
|
|
49
|
+
const mesh = createAdvancedCube(2, { material: 'emissive', emissive: 0x00ff00, intensity: 3 }, [
|
|
50
|
+
x,
|
|
51
|
+
y,
|
|
52
|
+
z,
|
|
53
|
+
]);
|
|
54
|
+
dataPackets.push({ mesh, x, y, z, active: true, offset: Math.random() * 10 });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function spawnDrones() {
|
|
59
|
+
for (let i = 0; i < 8; i++) {
|
|
60
|
+
const x = (Math.random() - 0.5) * CITY_SIZE * 1.2;
|
|
61
|
+
const y = 8 + Math.random() * 15;
|
|
62
|
+
const z = (Math.random() - 0.5) * CITY_SIZE * 1.2;
|
|
63
|
+
const body = createCube(1.5, 0.6, 1.5, 0xff2222, [x, y, z]);
|
|
64
|
+
const glow = createCube(1.8, 0.3, 1.8, 0xff0000, [x, y - 0.4, z]);
|
|
65
|
+
drones.push({
|
|
66
|
+
body,
|
|
67
|
+
glow,
|
|
68
|
+
x,
|
|
69
|
+
y,
|
|
70
|
+
z,
|
|
71
|
+
vx: 0,
|
|
72
|
+
vy: 0,
|
|
73
|
+
vz: 0,
|
|
74
|
+
hp: 60,
|
|
75
|
+
alive: true,
|
|
76
|
+
shootCD: 2 + Math.random() * 2,
|
|
77
|
+
target: { x: (Math.random() - 0.5) * CITY_SIZE, y, z: (Math.random() - 0.5) * CITY_SIZE },
|
|
78
|
+
waypointTimer: 3 + Math.random() * 4,
|
|
79
|
+
respawnTimer: 0,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function updateDrones(dt) {
|
|
85
|
+
for (let i = 0; i < drones.length; i++) {
|
|
86
|
+
const d = drones[i];
|
|
87
|
+
if (!d.alive) {
|
|
88
|
+
d.respawnTimer -= dt;
|
|
89
|
+
if (d.respawnTimer <= 0) {
|
|
90
|
+
d.x = (Math.random() - 0.5) * CITY_SIZE * 1.2;
|
|
91
|
+
d.y = 8 + Math.random() * 15;
|
|
92
|
+
d.z = (Math.random() - 0.5) * CITY_SIZE * 1.2;
|
|
93
|
+
d.body = createCube(1.5, 0.6, 1.5, 0xff2222, [d.x, d.y, d.z]);
|
|
94
|
+
d.glow = createCube(1.8, 0.3, 1.8, 0xff0000, [d.x, d.y - 0.4, d.z]);
|
|
95
|
+
d.hp = 60;
|
|
96
|
+
d.alive = true;
|
|
97
|
+
d.shootCD = 2 + Math.random() * 2;
|
|
98
|
+
}
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const dx = player.x - d.x;
|
|
103
|
+
const dy = player.y - d.y;
|
|
104
|
+
const dz = player.z - d.z;
|
|
105
|
+
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
106
|
+
|
|
107
|
+
if (dist < 30) {
|
|
108
|
+
d.vx += (dx / dist) * 12 * dt;
|
|
109
|
+
d.vy += (dy / dist) * 12 * dt;
|
|
110
|
+
d.vz += (dz / dist) * 12 * dt;
|
|
111
|
+
|
|
112
|
+
d.shootCD -= dt;
|
|
113
|
+
if (d.shootCD <= 0 && dist < 25) {
|
|
114
|
+
d.shootCD = 1.5 + Math.random();
|
|
115
|
+
const pspd = 30;
|
|
116
|
+
droneProjectiles.push({
|
|
117
|
+
mesh: createSphere(0.3, 0xff4444, [d.x, d.y, d.z]),
|
|
118
|
+
x: d.x,
|
|
119
|
+
y: d.y,
|
|
120
|
+
z: d.z,
|
|
121
|
+
vx: (dx / dist) * pspd,
|
|
122
|
+
vy: (dy / dist) * pspd,
|
|
123
|
+
vz: (dz / dist) * pspd,
|
|
124
|
+
life: 3,
|
|
125
|
+
});
|
|
126
|
+
sfx('laser');
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
d.waypointTimer -= dt;
|
|
130
|
+
if (d.waypointTimer <= 0) {
|
|
131
|
+
d.target.x = (Math.random() - 0.5) * CITY_SIZE;
|
|
132
|
+
d.target.z = (Math.random() - 0.5) * CITY_SIZE;
|
|
133
|
+
d.target.y = 8 + Math.random() * 15;
|
|
134
|
+
d.waypointTimer = 4 + Math.random() * 4;
|
|
135
|
+
}
|
|
136
|
+
const tx = d.target.x - d.x;
|
|
137
|
+
const ty = d.target.y - d.y;
|
|
138
|
+
const tz = d.target.z - d.z;
|
|
139
|
+
const td = Math.sqrt(tx * tx + ty * ty + tz * tz) || 1;
|
|
140
|
+
d.vx += (tx / td) * 6 * dt;
|
|
141
|
+
d.vy += (ty / td) * 6 * dt;
|
|
142
|
+
d.vz += (tz / td) * 6 * dt;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
d.vx *= 0.95;
|
|
146
|
+
d.vy *= 0.95;
|
|
147
|
+
d.vz *= 0.95;
|
|
148
|
+
d.x += d.vx * dt;
|
|
149
|
+
d.y += d.vy * dt;
|
|
150
|
+
d.z += d.vz * dt;
|
|
151
|
+
setPosition(d.body, d.x, d.y, d.z);
|
|
152
|
+
setPosition(d.glow, d.x, d.y - 0.4, d.z);
|
|
153
|
+
setRotation(d.body, 0, gameTime * 3, 0);
|
|
154
|
+
|
|
155
|
+
// Player ram damage when boosting
|
|
156
|
+
if (dist < 3 && player.boost > 1.5) {
|
|
157
|
+
d.hp -= 40;
|
|
158
|
+
sfx('hit');
|
|
159
|
+
triggerShake(shake, 0.5);
|
|
160
|
+
if (d.hp <= 0) {
|
|
161
|
+
d.alive = false;
|
|
162
|
+
d.respawnTimer = 8;
|
|
163
|
+
destroyMesh(d.body);
|
|
164
|
+
destroyMesh(d.glow);
|
|
165
|
+
dronesDestroyed++;
|
|
166
|
+
playerScore += 200;
|
|
167
|
+
sfx('explosion');
|
|
168
|
+
for (let j = 0; j < 10; j++) {
|
|
169
|
+
const p = createSphere(0.2, 0xff4400, [d.x, d.y, d.z]);
|
|
170
|
+
particles.push({
|
|
171
|
+
mesh: p,
|
|
172
|
+
x: d.x,
|
|
173
|
+
y: d.y,
|
|
174
|
+
z: d.z,
|
|
175
|
+
vx: (Math.random() - 0.5) * 15,
|
|
176
|
+
vy: (Math.random() - 0.5) * 15,
|
|
177
|
+
vz: (Math.random() - 0.5) * 15,
|
|
178
|
+
life: 1.5,
|
|
179
|
+
maxLife: 1.5,
|
|
180
|
+
type: 'explosion',
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
if (currentMission && currentMission.type === 'DRONE_SWEEP') {
|
|
184
|
+
currentMission.progress++;
|
|
185
|
+
}
|
|
186
|
+
playerHealth = Math.min(playerMaxHealth, playerHealth + 10);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function updateProjectiles(dt) {
|
|
193
|
+
for (let i = droneProjectiles.length - 1; i >= 0; i--) {
|
|
194
|
+
const p = droneProjectiles[i];
|
|
195
|
+
p.x += p.vx * dt;
|
|
196
|
+
p.y += p.vy * dt;
|
|
197
|
+
p.z += p.vz * dt;
|
|
198
|
+
p.life -= dt;
|
|
199
|
+
setPosition(p.mesh, p.x, p.y, p.z);
|
|
200
|
+
|
|
201
|
+
const dx = p.x - player.x;
|
|
202
|
+
const dy = p.y - player.y;
|
|
203
|
+
const dz = p.z - player.z;
|
|
204
|
+
if (Math.sqrt(dx * dx + dy * dy + dz * dz) < 3 && playerDmgCD <= 0) {
|
|
205
|
+
playerHealth = Math.max(0, playerHealth - 10);
|
|
206
|
+
playerDmgCD = 0.8;
|
|
207
|
+
triggerShake(shake, 0.4);
|
|
208
|
+
sfx('hit');
|
|
209
|
+
destroyMesh(p.mesh);
|
|
210
|
+
droneProjectiles.splice(i, 1);
|
|
211
|
+
if (playerHealth <= 0) {
|
|
212
|
+
playerHealth = playerMaxHealth;
|
|
213
|
+
playerScore = Math.max(0, playerScore - 500);
|
|
214
|
+
missionMsg = 'SYSTEM CRASH - REBOOTING...';
|
|
215
|
+
missionMsgTimer = 3;
|
|
216
|
+
sfx('death');
|
|
217
|
+
}
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (p.life <= 0) {
|
|
222
|
+
destroyMesh(p.mesh);
|
|
223
|
+
droneProjectiles.splice(i, 1);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const MISSION_TYPES = ['DATA_HEIST', 'DRONE_SWEEP', 'SPEED_RUN'];
|
|
229
|
+
|
|
230
|
+
function startNextMission() {
|
|
231
|
+
removeCheckpoint();
|
|
232
|
+
missionIndex++;
|
|
233
|
+
const type = MISSION_TYPES[(missionIndex - 1) % MISSION_TYPES.length];
|
|
234
|
+
const tier = Math.floor((missionIndex - 1) / 3) + 1;
|
|
235
|
+
|
|
236
|
+
if (type === 'DATA_HEIST') {
|
|
237
|
+
currentMission = {
|
|
238
|
+
type,
|
|
239
|
+
name: `DATA HEIST #${missionIndex}`,
|
|
240
|
+
desc: `Collect ${2 + tier} data packets`,
|
|
241
|
+
target: 2 + tier,
|
|
242
|
+
progress: 0,
|
|
243
|
+
timeLimit: 45 + tier * 10,
|
|
244
|
+
reward: 500 * tier,
|
|
245
|
+
};
|
|
246
|
+
respawnPackets(currentMission.target + 3);
|
|
247
|
+
} else if (type === 'DRONE_SWEEP') {
|
|
248
|
+
currentMission = {
|
|
249
|
+
type,
|
|
250
|
+
name: `DRONE SWEEP #${missionIndex}`,
|
|
251
|
+
desc: `Destroy ${1 + tier} drones`,
|
|
252
|
+
target: 1 + tier,
|
|
253
|
+
progress: 0,
|
|
254
|
+
timeLimit: 60 + tier * 15,
|
|
255
|
+
reward: 800 * tier,
|
|
256
|
+
};
|
|
257
|
+
} else {
|
|
258
|
+
const cx = (Math.random() - 0.5) * CITY_SIZE * 0.8;
|
|
259
|
+
const cz = (Math.random() - 0.5) * CITY_SIZE * 0.8;
|
|
260
|
+
currentMission = {
|
|
261
|
+
type,
|
|
262
|
+
name: `SPEED RUN #${missionIndex}`,
|
|
263
|
+
desc: 'Reach the checkpoint!',
|
|
264
|
+
target: 1,
|
|
265
|
+
progress: 0,
|
|
266
|
+
timeLimit: 25 + tier * 5,
|
|
267
|
+
reward: 400 * tier,
|
|
268
|
+
cx,
|
|
269
|
+
cz,
|
|
270
|
+
};
|
|
271
|
+
createCheckpointMarker(cx, 10, cz);
|
|
272
|
+
}
|
|
273
|
+
missionTimer = currentMission.timeLimit;
|
|
274
|
+
missionMsg = `NEW: ${currentMission.name}`;
|
|
275
|
+
missionMsgTimer = 3;
|
|
276
|
+
sfx('powerup');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function respawnPackets(count) {
|
|
280
|
+
const active = dataPackets.filter(p => p.active).length;
|
|
281
|
+
const needed = Math.max(0, count - active);
|
|
282
|
+
for (let i = 0; i < needed; i++) {
|
|
283
|
+
const x = (Math.random() - 0.5) * CITY_SIZE * 1.2;
|
|
284
|
+
const y = 3 + Math.random() * 20;
|
|
285
|
+
const z = (Math.random() - 0.5) * CITY_SIZE * 1.2;
|
|
286
|
+
const mesh = createAdvancedCube(2, { material: 'emissive', emissive: 0x00ff00, intensity: 3 }, [
|
|
287
|
+
x,
|
|
288
|
+
y,
|
|
289
|
+
z,
|
|
290
|
+
]);
|
|
291
|
+
dataPackets.push({ mesh, x, y, z, active: true, offset: Math.random() * 10 });
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function createCheckpointMarker(x, y, z) {
|
|
296
|
+
checkpointMesh = createCylinder(1, 30, 0x00ffff, [x, y, z], { segments: 6 });
|
|
297
|
+
checkpointGlow = createCylinder(2, 32, 0x004488, [x, y - 1, z], { segments: 6 });
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function removeCheckpoint() {
|
|
301
|
+
if (checkpointMesh) {
|
|
302
|
+
destroyMesh(checkpointMesh);
|
|
303
|
+
checkpointMesh = null;
|
|
304
|
+
}
|
|
305
|
+
if (checkpointGlow) {
|
|
306
|
+
destroyMesh(checkpointGlow);
|
|
307
|
+
checkpointGlow = null;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function updateMission(dt) {
|
|
312
|
+
if (!currentMission) return;
|
|
313
|
+
missionTimer -= dt;
|
|
314
|
+
if (missionMsgTimer > 0) missionMsgTimer -= dt;
|
|
315
|
+
|
|
316
|
+
if (currentMission.progress >= currentMission.target) {
|
|
317
|
+
playerScore += currentMission.reward;
|
|
318
|
+
missionMsg = `COMPLETE! +${currentMission.reward} CREDITS`;
|
|
319
|
+
missionMsgTimer = 3;
|
|
320
|
+
sfx('coin');
|
|
321
|
+
triggerShake(shake, 0.3);
|
|
322
|
+
playerHealth = Math.min(playerMaxHealth, playerHealth + 20);
|
|
323
|
+
const m = currentMission;
|
|
324
|
+
currentMission = null;
|
|
325
|
+
setTimeout(() => startNextMission(), 2500);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (currentMission.type === 'SPEED_RUN') {
|
|
330
|
+
const dx = player.x - currentMission.cx;
|
|
331
|
+
const dz = player.z - currentMission.cz;
|
|
332
|
+
if (Math.sqrt(dx * dx + dz * dz) < 8) {
|
|
333
|
+
currentMission.progress = 1;
|
|
334
|
+
}
|
|
335
|
+
if (checkpointMesh) setRotation(checkpointMesh, 0, gameTime * 2, 0);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (missionTimer <= 0) {
|
|
339
|
+
missionMsg = 'MISSION FAILED - RETRYING';
|
|
340
|
+
missionMsgTimer = 2;
|
|
341
|
+
sfx('error');
|
|
342
|
+
currentMission.progress = 0;
|
|
343
|
+
missionTimer = currentMission.timeLimit;
|
|
344
|
+
if (currentMission.type === 'SPEED_RUN') {
|
|
345
|
+
removeCheckpoint();
|
|
346
|
+
currentMission.cx = (Math.random() - 0.5) * CITY_SIZE * 0.8;
|
|
347
|
+
currentMission.cz = (Math.random() - 0.5) * CITY_SIZE * 0.8;
|
|
348
|
+
createCheckpointMarker(currentMission.cx, 10, currentMission.cz);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// City configuration
|
|
354
|
+
const CITY_SIZE = 100;
|
|
355
|
+
const BUILDING_COUNT = 50;
|
|
356
|
+
// GPU instancing for city grid elements
|
|
357
|
+
let glowSphereInstanceId = null;
|
|
358
|
+
const VEHICLE_COUNT = 12;
|
|
359
|
+
const PARTICLE_COUNT = 200;
|
|
360
|
+
|
|
361
|
+
// 🌈 NEON COLORS - Bright and vibrant!
|
|
362
|
+
const COLORS = {
|
|
363
|
+
building: [0x4a4a7a, 0x5a5a8a, 0x6a6a9a, 0x3a3a6a], // Brighter buildings
|
|
364
|
+
neon: [0xff0099, 0x00ff99, 0x9900ff, 0xffff00, 0x00ffff, 0xff6600], // Intense neons
|
|
365
|
+
neonGlow: [0xff55cc, 0x55ffcc, 0xcc55ff, 0xffffaa, 0xaaffff, 0xffaa66], // Glow halos
|
|
366
|
+
vehicle: [0xff6666, 0x66ff66, 0x6666ff, 0xffff66, 0xff66ff], // Brighter vehicles
|
|
367
|
+
particle: [0xffaa00, 0xaa00ff, 0x00ffaa, 0xff00aa, 0xaaffff, 0xffaaff], // Glowing particles
|
|
368
|
+
underglow: [0x00ffff, 0xff00ff, 0xffff00, 0x00ff00, 0xff0000], // Vehicle underglows
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
export async function init() {
|
|
372
|
+
cls();
|
|
373
|
+
|
|
374
|
+
// Setup dramatic 3D scene
|
|
375
|
+
setCameraPosition(0, 20, 30);
|
|
376
|
+
setCameraTarget(0, 10, 0);
|
|
377
|
+
setCameraFOV(75);
|
|
378
|
+
|
|
379
|
+
// 🌈 BRIGHT NEON LIGHTING - Make it pop!
|
|
380
|
+
setLightDirection(-0.3, -0.7, -0.4);
|
|
381
|
+
setLightColor(0xffaaff); // Bright magenta/pink key light
|
|
382
|
+
setAmbientLight(0x664488); // Much brighter purple ambient
|
|
383
|
+
|
|
384
|
+
// 🌫️ Atmospheric fog with neon tint
|
|
385
|
+
setFog(0x441166, 30, 180); // Purple/magenta fog
|
|
386
|
+
|
|
387
|
+
// 🎨 Enable ALL visual effects for maximum impact
|
|
388
|
+
enablePixelation(1);
|
|
389
|
+
enableDithering(true);
|
|
390
|
+
enableBloom(1.5, 0.3, 0.25); // Strong neon glow
|
|
391
|
+
enableFXAA(); // Anti-aliasing
|
|
392
|
+
enableChromaticAberration(0.003); // Cyberpunk lens distortion
|
|
393
|
+
enableVignette(1.6, 0.85); // Dark vignette for immersion
|
|
394
|
+
|
|
395
|
+
await buildCyberpunkCity();
|
|
396
|
+
createPlayer();
|
|
397
|
+
spawnVehicles();
|
|
398
|
+
initParticleSystem();
|
|
399
|
+
spawnPackets();
|
|
400
|
+
spawnDrones();
|
|
401
|
+
shake = createShake(0.3);
|
|
402
|
+
|
|
403
|
+
// Initialize start screen
|
|
404
|
+
initStartScreen();
|
|
405
|
+
|
|
406
|
+
console.log('🌃 CYBERPUNK CITY 3D - NEON ENHANCED!');
|
|
407
|
+
console.log('WASD: Move | SHIFT: Fly Mode | SPACE: Boost | X: Switch Vehicle');
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function initStartScreen() {
|
|
411
|
+
uiButtons = [];
|
|
412
|
+
|
|
413
|
+
uiButtons.push(
|
|
414
|
+
createButton(
|
|
415
|
+
centerX(240),
|
|
416
|
+
150,
|
|
417
|
+
240,
|
|
418
|
+
60,
|
|
419
|
+
'▶ ENTER THE CITY ▶',
|
|
420
|
+
() => {
|
|
421
|
+
gameState = 'exploring';
|
|
422
|
+
startNextMission();
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
normalColor: rgba8(255, 0, 100, 255),
|
|
426
|
+
hoverColor: rgba8(255, 60, 150, 255),
|
|
427
|
+
pressedColor: rgba8(200, 0, 80, 255),
|
|
428
|
+
}
|
|
429
|
+
)
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
uiButtons.push(
|
|
433
|
+
createButton(
|
|
434
|
+
centerX(200),
|
|
435
|
+
355,
|
|
436
|
+
200,
|
|
437
|
+
45,
|
|
438
|
+
'🎮 CONTROLS',
|
|
439
|
+
() => {
|
|
440
|
+
console.log('Cyberpunk City - WASD: Move, SHIFT: Fly, SPACE: Boost');
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
normalColor: rgba8(0, 255, 255, 255),
|
|
444
|
+
hoverColor: rgba8(60, 255, 255, 255),
|
|
445
|
+
pressedColor: rgba8(0, 200, 200, 255),
|
|
446
|
+
}
|
|
447
|
+
)
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
export function update(dt) {
|
|
452
|
+
gameTime += dt;
|
|
453
|
+
|
|
454
|
+
if (gameState === 'start') {
|
|
455
|
+
startScreenTime += dt;
|
|
456
|
+
updateAllButtons();
|
|
457
|
+
|
|
458
|
+
// Animate scene in background
|
|
459
|
+
updateVehicles(dt);
|
|
460
|
+
updateParticles(dt);
|
|
461
|
+
updatePackets(dt);
|
|
462
|
+
updateCityLights(dt);
|
|
463
|
+
updateNeonSigns(dt);
|
|
464
|
+
|
|
465
|
+
// Slow camera orbit
|
|
466
|
+
camera.x = Math.cos(gameTime * 0.2) * 40;
|
|
467
|
+
camera.z = Math.sin(gameTime * 0.2) * 40;
|
|
468
|
+
camera.y = 25;
|
|
469
|
+
setCameraPosition(camera.x, camera.y, camera.z);
|
|
470
|
+
setCameraTarget(0, 10, 0);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
handleInput(dt);
|
|
475
|
+
updatePlayer(dt);
|
|
476
|
+
updateVehicles(dt);
|
|
477
|
+
updateParticles(dt);
|
|
478
|
+
updatePackets(dt);
|
|
479
|
+
updateCityLights(dt);
|
|
480
|
+
updateCamera(dt);
|
|
481
|
+
updateNeonSigns(dt);
|
|
482
|
+
updateDrones(dt);
|
|
483
|
+
updateProjectiles(dt);
|
|
484
|
+
updateMission(dt);
|
|
485
|
+
if (shake) updateShake(shake, dt);
|
|
486
|
+
if (playerDmgCD > 0) playerDmgCD -= dt;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export function draw() {
|
|
490
|
+
if (gameState === 'start') {
|
|
491
|
+
drawStartScreen();
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// 3D scene automatically rendered by GPU
|
|
496
|
+
drawHUD();
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function drawStartScreen() {
|
|
500
|
+
// Neon gradient background — dual-band
|
|
501
|
+
drawGradient(0, 0, 640, 200, rgba8(50, 10, 50, 235), rgba8(10, 5, 20, 245), 'v');
|
|
502
|
+
drawGradient(0, 200, 640, 160, rgba8(10, 5, 20, 245), rgba8(20, 5, 40, 240), 'v');
|
|
503
|
+
|
|
504
|
+
// Animated noise "digital rain" static
|
|
505
|
+
drawNoise(0, 0, 640, 360, 22, Math.floor(startScreenTime * 30));
|
|
506
|
+
|
|
507
|
+
// Radial spotlight behind title
|
|
508
|
+
drawRadialGradient(320, 88, 200, rgba8(180, 0, 120, 35), rgba8(0, 0, 0, 0));
|
|
509
|
+
|
|
510
|
+
// Neon title with glow effect
|
|
511
|
+
const neonPulse = Math.sin(startScreenTime * 4) * 0.3 + 0.7;
|
|
512
|
+
const pinkNeon = rgba8(255, Math.floor(neonPulse * 100), Math.floor(neonPulse * 200), 255);
|
|
513
|
+
const cyanNeon = rgba8(0, Math.floor(neonPulse * 255), 255, 255);
|
|
514
|
+
|
|
515
|
+
const flicker = Math.random() > 0.95 ? -2 : 0;
|
|
516
|
+
drawGlowTextCentered('CYBERPUNK', 320, 50 + flicker, pinkNeon, rgba8(150, 0, 100, 150), 2);
|
|
517
|
+
drawGlowTextCentered('CITY 3D', 320, 105 + flicker, cyanNeon, rgba8(0, 80, 140, 150), 2);
|
|
518
|
+
|
|
519
|
+
// Glitch subtitle
|
|
520
|
+
const glitch = Math.random() > 0.97 ? Math.floor(Math.random() * 4) - 2 : 0;
|
|
521
|
+
setFont('large');
|
|
522
|
+
setTextAlign('center');
|
|
523
|
+
drawText('▶ Nintendo 64 / PlayStation Style ◀', 320 + glitch, 162, rgba8(255, 255, 0, 255), 1);
|
|
524
|
+
|
|
525
|
+
// Info panel
|
|
526
|
+
const panel = createPanel(centerX(480), 208, 480, 118, {
|
|
527
|
+
bgColor: rgba8(30, 10, 30, 210),
|
|
528
|
+
borderColor: rgba8(255, 0, 100, 255),
|
|
529
|
+
borderWidth: 3,
|
|
530
|
+
shadow: true,
|
|
531
|
+
gradient: true,
|
|
532
|
+
gradientColor: rgba8(50, 20, 50, 210),
|
|
533
|
+
});
|
|
534
|
+
drawPanel(panel);
|
|
535
|
+
|
|
536
|
+
setFont('normal');
|
|
537
|
+
setTextAlign('center');
|
|
538
|
+
drawText('EXPLORE THE NEON METROPOLIS', 320, 225, rgba8(255, 0, 255, 255), 1);
|
|
539
|
+
|
|
540
|
+
setFont('small');
|
|
541
|
+
drawText('▶ 50+ Procedural Buildings with Neon Lights', 320, 247, uiColors.light, 1);
|
|
542
|
+
drawText('▶ Flying Vehicles & Dynamic Particle System', 320, 262, uiColors.light, 1);
|
|
543
|
+
drawText('▶ Full Player Control + Flying Mode', 320, 277, uiColors.light, 1);
|
|
544
|
+
drawText('▶ Retro N64 Effects: Pixelation, Dithering, Bloom', 320, 292, uiColors.light, 1);
|
|
545
|
+
|
|
546
|
+
setFont('tiny');
|
|
547
|
+
drawText('WASD: Move | SHIFT: Fly | SPACE: Boost', 320, 310, uiColors.secondary, 1);
|
|
548
|
+
|
|
549
|
+
// Draw buttons
|
|
550
|
+
drawAllButtons();
|
|
551
|
+
|
|
552
|
+
// Animated neon wave at horizon
|
|
553
|
+
drawWave(0, 348, 640, 7, 0.032, startScreenTime * 2.5, rgba8(255, 0, 255, 110), 2);
|
|
554
|
+
drawWave(0, 353, 640, 5, 0.045, startScreenTime * 2.5 + 1.2, rgba8(0, 255, 255, 85), 2);
|
|
555
|
+
|
|
556
|
+
// Pulsing neon prompt
|
|
557
|
+
const alpha = Math.floor((Math.sin(startScreenTime * 6) * 0.5 + 0.5) * 255);
|
|
558
|
+
setFont('normal');
|
|
559
|
+
drawText('▶ WELCOME TO THE FUTURE ◀', 320, 430, rgba8(255, 0, 255, alpha), 1);
|
|
560
|
+
|
|
561
|
+
// Build info
|
|
562
|
+
setFont('tiny');
|
|
563
|
+
drawText('GPU-Accelerated 3D City Simulation', 320, 338, rgba8(150, 100, 200, 150), 1);
|
|
564
|
+
|
|
565
|
+
// CRT scanlines
|
|
566
|
+
drawScanlines(52, 2);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
async function buildCyberpunkCity() {
|
|
570
|
+
// 🌃 Create ground with brighter base
|
|
571
|
+
const ground = createPlane(CITY_SIZE * 2, CITY_SIZE * 2, 0x2a2a55, [0, 0, 0]);
|
|
572
|
+
setRotation(ground, -Math.PI / 2, 0, 0);
|
|
573
|
+
|
|
574
|
+
// ⚡ Add BRIGHT NEON grid lines for cyberpunk aesthetic
|
|
575
|
+
for (let i = -CITY_SIZE; i <= CITY_SIZE; i += 10) {
|
|
576
|
+
// Horizontal lines - CYAN neon
|
|
577
|
+
createCube(CITY_SIZE * 2, 0.15, 0.3, 0x00ffff, [0, 0.15, i]);
|
|
578
|
+
// Vertical lines - MAGENTA neon
|
|
579
|
+
createCube(0.3, 0.15, CITY_SIZE * 2, 0xff00ff, [i, 0.15, 0]);
|
|
580
|
+
|
|
581
|
+
// Add glow effect underneath
|
|
582
|
+
createCube(CITY_SIZE * 2, 0.05, 0.5, 0x0088aa, [0, 0.05, i]);
|
|
583
|
+
createCube(0.5, 0.05, CITY_SIZE * 2, 0xaa0088, [i, 0.05, 0]);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// ✨ Intersection glow points — GPU instanced (121 spheres → 1 draw call)
|
|
587
|
+
const gridCount = Math.ceil((CITY_SIZE * 2) / 20 + 1);
|
|
588
|
+
const totalGlows = gridCount * gridCount;
|
|
589
|
+
glowSphereInstanceId = createInstancedMesh('sphere', totalGlows, 0x00ffff, {
|
|
590
|
+
size: 0.5,
|
|
591
|
+
segments: 6,
|
|
592
|
+
emissive: 0x008888,
|
|
593
|
+
emissiveIntensity: 1.0,
|
|
594
|
+
});
|
|
595
|
+
let glowIdx = 0;
|
|
596
|
+
for (let i = -CITY_SIZE; i <= CITY_SIZE; i += 20) {
|
|
597
|
+
for (let j = -CITY_SIZE; j <= CITY_SIZE; j += 20) {
|
|
598
|
+
const neonIdx = Math.floor(Math.random() * COLORS.neon.length);
|
|
599
|
+
setInstanceTransform(glowSphereInstanceId, glowIdx, i, 0.5, j);
|
|
600
|
+
setInstanceColor(glowSphereInstanceId, glowIdx, COLORS.neon[neonIdx]);
|
|
601
|
+
glowIdx++;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
finalizeInstances(glowSphereInstanceId);
|
|
605
|
+
|
|
606
|
+
// Generate procedural buildings
|
|
607
|
+
for (let i = 0; i < BUILDING_COUNT; i++) {
|
|
608
|
+
await createBuilding(i);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// Create central megastructure
|
|
612
|
+
await createMegaStructure();
|
|
613
|
+
|
|
614
|
+
// Add flying platforms
|
|
615
|
+
for (let i = 0; i < 8; i++) {
|
|
616
|
+
const angle = (i / 8) * Math.PI * 2;
|
|
617
|
+
const radius = 40;
|
|
618
|
+
const platform = createCube(8, 1, 8, 0x666699, [
|
|
619
|
+
Math.cos(angle) * radius,
|
|
620
|
+
15 + Math.sin(gameTime + i) * 3,
|
|
621
|
+
Math.sin(angle) * radius,
|
|
622
|
+
]);
|
|
623
|
+
|
|
624
|
+
// Add neon underglow
|
|
625
|
+
const glow = createCube(8.5, 0.2, 8.5, COLORS.neon[i % COLORS.neon.length], [
|
|
626
|
+
Math.cos(angle) * radius,
|
|
627
|
+
14.5 + Math.sin(gameTime + i) * 3,
|
|
628
|
+
Math.sin(angle) * radius,
|
|
629
|
+
]);
|
|
630
|
+
|
|
631
|
+
cityObjects.push({ type: 'platform', mesh: platform, glow: glow, angle: angle, index: i });
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
async function createBuilding(index) {
|
|
636
|
+
const x = (Math.random() - 0.5) * CITY_SIZE * 1.5;
|
|
637
|
+
const z = (Math.random() - 0.5) * CITY_SIZE * 1.5;
|
|
638
|
+
|
|
639
|
+
// Avoid center area
|
|
640
|
+
if (Math.abs(x) < 15 && Math.abs(z) < 15) return;
|
|
641
|
+
|
|
642
|
+
const width = 3 + Math.random() * 6;
|
|
643
|
+
const depth = 3 + Math.random() * 6;
|
|
644
|
+
const height = 8 + Math.random() * 25;
|
|
645
|
+
|
|
646
|
+
// Main building
|
|
647
|
+
const building = createCube(
|
|
648
|
+
width,
|
|
649
|
+
height,
|
|
650
|
+
depth,
|
|
651
|
+
COLORS.building[index % COLORS.building.length],
|
|
652
|
+
[x, height / 2, z]
|
|
653
|
+
);
|
|
654
|
+
|
|
655
|
+
// 🎨 Add COLORFUL detail layers (no more black blocks!)
|
|
656
|
+
const detailColor1 = COLORS.neon[index % COLORS.neon.length];
|
|
657
|
+
const detailColor2 = COLORS.neonGlow[(index + 2) % COLORS.neonGlow.length];
|
|
658
|
+
const detail1 = createCube(width * 0.9, height * 0.3, depth * 0.9, detailColor1, [
|
|
659
|
+
x,
|
|
660
|
+
height * 0.15,
|
|
661
|
+
z,
|
|
662
|
+
]);
|
|
663
|
+
const detail2 = createCube(width * 0.8, height * 0.2, depth * 0.8, detailColor2, [
|
|
664
|
+
x,
|
|
665
|
+
height * 0.9,
|
|
666
|
+
z,
|
|
667
|
+
]);
|
|
668
|
+
|
|
669
|
+
// 💡 Windows with BRIGHT NEON animated lighting
|
|
670
|
+
const windowRows = Math.floor(height / 3);
|
|
671
|
+
const windows = [];
|
|
672
|
+
|
|
673
|
+
for (let row = 0; row < windowRows; row++) {
|
|
674
|
+
for (let col = 0; col < 3; col++) {
|
|
675
|
+
const windowX = x + (col - 1) * width * 0.25;
|
|
676
|
+
const windowY = 2 + row * 3;
|
|
677
|
+
const windowZ = z + depth * 0.51;
|
|
678
|
+
|
|
679
|
+
// Use BRIGHT neon glow colors for windows
|
|
680
|
+
const windowColor = COLORS.neonGlow[(row * 3 + col) % COLORS.neonGlow.length];
|
|
681
|
+
const window = createCube(0.8, 0.8, 0.1, windowColor, [windowX, windowY, windowZ]);
|
|
682
|
+
|
|
683
|
+
// Add window glow halo (brighter larger cube behind)
|
|
684
|
+
createCube(1.2, 1.2, 0.05, windowColor, [windowX, windowY, windowZ - 0.1]);
|
|
685
|
+
|
|
686
|
+
windows.push({
|
|
687
|
+
mesh: window,
|
|
688
|
+
flickerTime: Math.random() * 10,
|
|
689
|
+
baseColor: windowColor,
|
|
690
|
+
dimColor: windowColor & 0x555555, // Dim by masking bits
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// ⚡ Neon sign on top (50% chance, BRIGHT)
|
|
696
|
+
if (Math.random() < 0.5) {
|
|
697
|
+
const signColor = COLORS.neon[Math.floor(Math.random() * COLORS.neon.length)];
|
|
698
|
+
const glowColor = COLORS.neonGlow[Math.floor(Math.random() * COLORS.neonGlow.length)];
|
|
699
|
+
|
|
700
|
+
// Main sign
|
|
701
|
+
const sign = createCube(width * 1.2, 1, 0.2, signColor, [x, height + 1, z]);
|
|
702
|
+
|
|
703
|
+
// Glow halo around sign (larger, behind)
|
|
704
|
+
const signGlow = createCube(width * 1.4, 1.5, 0.1, glowColor, [x, height + 1, z - 0.2]);
|
|
705
|
+
|
|
706
|
+
neonSigns.push({
|
|
707
|
+
mesh: sign,
|
|
708
|
+
glow: signGlow,
|
|
709
|
+
color: signColor,
|
|
710
|
+
glowColor: glowColor,
|
|
711
|
+
pulsePhase: Math.random() * Math.PI * 2,
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
buildings.push({
|
|
716
|
+
main: building,
|
|
717
|
+
details: [detail1, detail2],
|
|
718
|
+
windows: windows,
|
|
719
|
+
x: x,
|
|
720
|
+
z: z,
|
|
721
|
+
height: height,
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
async function createMegaStructure() {
|
|
726
|
+
// 🏙️ Central tower (BRIGHT PURPLE with neon accents)
|
|
727
|
+
createCube(12, 60, 12, 0x8855cc, [0, 30, 0]);
|
|
728
|
+
|
|
729
|
+
// Add colorful stripes to tower
|
|
730
|
+
for (let i = 0; i < 10; i++) {
|
|
731
|
+
const stripeColor = COLORS.neon[i % COLORS.neon.length];
|
|
732
|
+
createCube(12.5, 2, 12.5, stripeColor, [0, 6 + i * 6, 0]);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// 🌉 Connecting bridges (BRIGHT CYAN)
|
|
736
|
+
for (let i = 0; i < 4; i++) {
|
|
737
|
+
const angle = (i / 4) * Math.PI * 2;
|
|
738
|
+
const bridgeX = Math.cos(angle) * 20;
|
|
739
|
+
const bridgeZ = Math.sin(angle) * 20;
|
|
740
|
+
|
|
741
|
+
const bridgeColor = COLORS.neonGlow[i % COLORS.neonGlow.length];
|
|
742
|
+
const bridge = createCube(16, 2, 4, bridgeColor, [bridgeX / 2, 25, bridgeZ / 2]);
|
|
743
|
+
setRotation(bridge, 0, angle, 0);
|
|
744
|
+
|
|
745
|
+
// Add underglow to bridges
|
|
746
|
+
const bridgeGlow = createCube(17, 0.5, 4.5, COLORS.underglow[i % COLORS.underglow.length], [
|
|
747
|
+
bridgeX / 2,
|
|
748
|
+
24,
|
|
749
|
+
bridgeZ / 2,
|
|
750
|
+
]);
|
|
751
|
+
setRotation(bridgeGlow, 0, angle, 0);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Antenna array on top
|
|
755
|
+
for (let i = 0; i < 6; i++) {
|
|
756
|
+
const antenna = createCube(0.3, 8, 0.3, 0xffffff, [
|
|
757
|
+
Math.random() * 8 - 4,
|
|
758
|
+
64,
|
|
759
|
+
Math.random() * 8 - 4,
|
|
760
|
+
]);
|
|
761
|
+
|
|
762
|
+
// Blinking light on antenna
|
|
763
|
+
const light = createSphere(0.5, 0xff0000, [Math.random() * 8 - 4, 68, Math.random() * 8 - 4]);
|
|
764
|
+
|
|
765
|
+
cityLights.push({
|
|
766
|
+
mesh: light,
|
|
767
|
+
blinkTime: Math.random() * 2,
|
|
768
|
+
isOn: true,
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function createPlayer() {
|
|
774
|
+
// Sleek hovercar
|
|
775
|
+
const body = createCube(3, 0.8, 1.5, 0x4444ff, [0, 2, 0]);
|
|
776
|
+
const cockpit = createSphere(1, 0x2222aa, [0, 2.8, 0.2]);
|
|
777
|
+
|
|
778
|
+
// Thrusters
|
|
779
|
+
const thruster1 = createCube(0.4, 0.4, 0.8, 0xff4400, [-1.2, 1.8, -0.8]);
|
|
780
|
+
const thruster2 = createCube(0.4, 0.4, 0.8, 0xff4400, [1.2, 1.8, -0.8]);
|
|
781
|
+
|
|
782
|
+
// Underglow
|
|
783
|
+
const glow = createCube(3.5, 0.2, 2, 0x00ffff, [0, 1.2, 0]);
|
|
784
|
+
|
|
785
|
+
player = {
|
|
786
|
+
body: body,
|
|
787
|
+
cockpit: cockpit,
|
|
788
|
+
thrusters: [thruster1, thruster2],
|
|
789
|
+
glow: glow,
|
|
790
|
+
x: 0,
|
|
791
|
+
y: 2,
|
|
792
|
+
z: 0,
|
|
793
|
+
vx: 0,
|
|
794
|
+
vy: 0,
|
|
795
|
+
vz: 0,
|
|
796
|
+
rotation: 0,
|
|
797
|
+
tilt: 0,
|
|
798
|
+
boost: 1,
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function spawnVehicles() {
|
|
803
|
+
for (let i = 0; i < VEHICLE_COUNT; i++) {
|
|
804
|
+
const vehicle = createTrafficVehicle(i);
|
|
805
|
+
vehicles.push(vehicle);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function createTrafficVehicle(index) {
|
|
810
|
+
const x = (Math.random() - 0.5) * CITY_SIZE;
|
|
811
|
+
const z = (Math.random() - 0.5) * CITY_SIZE;
|
|
812
|
+
const y = 1.5 + Math.random() * 8;
|
|
813
|
+
|
|
814
|
+
const color = COLORS.vehicle[index % COLORS.vehicle.length];
|
|
815
|
+
|
|
816
|
+
// 🚗 Main vehicle body
|
|
817
|
+
const body = createCube(2.5, 0.6, 1.2, color, [x, y, z]);
|
|
818
|
+
|
|
819
|
+
// ⚡ BRIGHT NEON UNDERGLOW (use underglow colors)
|
|
820
|
+
const underglowColor = COLORS.underglow[index % COLORS.underglow.length];
|
|
821
|
+
const glow = createCube(3.2, 0.3, 1.8, underglowColor, [x, y - 0.5, z]);
|
|
822
|
+
|
|
823
|
+
// ✨ Add second glow layer for extra brightness
|
|
824
|
+
const glow2 = createCube(3.5, 0.15, 2.0, underglowColor, [x, y - 0.6, z]);
|
|
825
|
+
|
|
826
|
+
return {
|
|
827
|
+
body: body,
|
|
828
|
+
glow: glow,
|
|
829
|
+
glow2: glow2,
|
|
830
|
+
underglowColor: underglowColor,
|
|
831
|
+
x: x,
|
|
832
|
+
y: y,
|
|
833
|
+
z: z,
|
|
834
|
+
vx: (Math.random() - 0.5) * 8,
|
|
835
|
+
vy: 0,
|
|
836
|
+
vz: (Math.random() - 0.5) * 8,
|
|
837
|
+
target: { x: x, y: y, z: z },
|
|
838
|
+
speed: 2 + Math.random() * 4,
|
|
839
|
+
turnRate: Math.random() * 2 + 1,
|
|
840
|
+
nextWaypoint: Math.random() * 10,
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function initParticleSystem() {
|
|
845
|
+
// Create ambient particles (dust, sparks, etc.)
|
|
846
|
+
for (let i = 0; i < PARTICLE_COUNT; i++) {
|
|
847
|
+
createAmbientParticle();
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
function createAmbientParticle() {
|
|
852
|
+
const x = (Math.random() - 0.5) * CITY_SIZE * 2;
|
|
853
|
+
const y = Math.random() * 40;
|
|
854
|
+
const z = (Math.random() - 0.5) * CITY_SIZE * 2;
|
|
855
|
+
|
|
856
|
+
const particle = createSphere(
|
|
857
|
+
0.1,
|
|
858
|
+
COLORS.particle[Math.floor(Math.random() * COLORS.particle.length)],
|
|
859
|
+
[x, y, z]
|
|
860
|
+
);
|
|
861
|
+
|
|
862
|
+
particles.push({
|
|
863
|
+
mesh: particle,
|
|
864
|
+
x: x,
|
|
865
|
+
y: y,
|
|
866
|
+
z: z,
|
|
867
|
+
vx: (Math.random() - 0.5) * 2,
|
|
868
|
+
vy: Math.random() * 0.5,
|
|
869
|
+
vz: (Math.random() - 0.5) * 2,
|
|
870
|
+
life: 5 + Math.random() * 10,
|
|
871
|
+
maxLife: 15,
|
|
872
|
+
type: 'ambient',
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function handleInput(dt) {
|
|
877
|
+
// 🚀 IMPROVED CONTROLS - Much more responsive!
|
|
878
|
+
const moveSpeed = flying ? 40 : 25; // Faster base speed
|
|
879
|
+
const accel = flying ? 60 : 45; // Snappy acceleration
|
|
880
|
+
const maxSpeed = flying ? 35 : 25; // Higher max speed
|
|
881
|
+
|
|
882
|
+
let inputX = 0;
|
|
883
|
+
let inputZ = 0;
|
|
884
|
+
|
|
885
|
+
// ⬅️➡️ Horizontal movement with smooth acceleration
|
|
886
|
+
if (btn(0)) {
|
|
887
|
+
// Left
|
|
888
|
+
inputX = -1;
|
|
889
|
+
player.tilt = Math.max(player.tilt - dt * 3, -0.4);
|
|
890
|
+
}
|
|
891
|
+
if (btn(1)) {
|
|
892
|
+
// Right
|
|
893
|
+
inputX = 1;
|
|
894
|
+
player.tilt = Math.min(player.tilt + dt * 3, 0.4);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// ⬆️⬇️ Forward/backward movement
|
|
898
|
+
if (btn(2)) {
|
|
899
|
+
// Up
|
|
900
|
+
inputZ = -1;
|
|
901
|
+
}
|
|
902
|
+
if (btn(3)) {
|
|
903
|
+
// Down
|
|
904
|
+
inputZ = 1;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// Apply acceleration with max speed cap
|
|
908
|
+
if (inputX !== 0) {
|
|
909
|
+
player.vx += inputX * accel * dt;
|
|
910
|
+
player.vx = Math.max(-maxSpeed, Math.min(maxSpeed, player.vx));
|
|
911
|
+
}
|
|
912
|
+
if (inputZ !== 0) {
|
|
913
|
+
player.vz += inputZ * accel * dt;
|
|
914
|
+
player.vz = Math.max(-maxSpeed, Math.min(maxSpeed, player.vz));
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// 🎮 Vertical movement (much more responsive)
|
|
918
|
+
if (btn(4)) {
|
|
919
|
+
// Z - Down
|
|
920
|
+
player.vy -= moveSpeed * dt * 0.8; // Faster vertical
|
|
921
|
+
}
|
|
922
|
+
if (btn(5)) {
|
|
923
|
+
// X - Up
|
|
924
|
+
player.vy += moveSpeed * dt * 0.8; // Faster vertical
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// 💨 BOOST - More powerful!
|
|
928
|
+
if (btnp(6)) {
|
|
929
|
+
// Space
|
|
930
|
+
player.boost = 4; // Stronger boost
|
|
931
|
+
const boostDir = { x: player.vx, z: player.vz };
|
|
932
|
+
const mag = Math.sqrt(boostDir.x * boostDir.x + boostDir.z * boostDir.z);
|
|
933
|
+
if (mag > 0) {
|
|
934
|
+
player.vx += (boostDir.x / mag) * 15; // Add velocity in current direction
|
|
935
|
+
player.vz += (boostDir.z / mag) * 15;
|
|
936
|
+
}
|
|
937
|
+
createBoostParticles();
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// ✈️ Flight mode toggle
|
|
941
|
+
if (btnp(7)) {
|
|
942
|
+
// Shift
|
|
943
|
+
flying = !flying;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// 🎯 Return tilt to center when not turning
|
|
947
|
+
if (inputX === 0) {
|
|
948
|
+
player.tilt *= 0.9; // Smooth return to center
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
function updatePlayer(dt) {
|
|
953
|
+
// 💨 Apply boost multiplier (decays smoothly)
|
|
954
|
+
let boostMult = 1;
|
|
955
|
+
if (player.boost > 1) {
|
|
956
|
+
boostMult = player.boost;
|
|
957
|
+
player.boost = Math.max(1, player.boost - dt * 4);
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// 🌍 Physics - Apply velocity to position
|
|
961
|
+
player.x += player.vx * boostMult * dt;
|
|
962
|
+
player.y += player.vy * dt;
|
|
963
|
+
player.z += player.vz * boostMult * dt;
|
|
964
|
+
|
|
965
|
+
// ✈️ Hover physics (auto-stabilize height when not flying)
|
|
966
|
+
if (!flying) {
|
|
967
|
+
const groundHeight = 2.5;
|
|
968
|
+
const hoverForce = (groundHeight - player.y) * 8; // Stronger hover
|
|
969
|
+
player.vy += hoverForce * dt;
|
|
970
|
+
|
|
971
|
+
// Clamp vertical position
|
|
972
|
+
if (player.y < groundHeight - 0.5) {
|
|
973
|
+
player.y = groundHeight - 0.5;
|
|
974
|
+
player.vy = Math.max(0, player.vy);
|
|
975
|
+
}
|
|
976
|
+
} else {
|
|
977
|
+
// In flight mode, add slight upward drift
|
|
978
|
+
player.vy += dt * 0.5;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// 🎮 IMPROVED DAMPING - Smoother deceleration
|
|
982
|
+
const dampFactor = flying ? 0.92 : 0.88; // More aggressive damping
|
|
983
|
+
player.vx *= dampFactor;
|
|
984
|
+
player.vz *= dampFactor;
|
|
985
|
+
player.vy *= 0.94; // Vertical damping
|
|
986
|
+
|
|
987
|
+
// Stop tiny movements (dead zone)
|
|
988
|
+
if (Math.abs(player.vx) < 0.1) player.vx = 0;
|
|
989
|
+
if (Math.abs(player.vz) < 0.1) player.vz = 0;
|
|
990
|
+
if (Math.abs(player.vy) < 0.1) player.vy = 0;
|
|
991
|
+
|
|
992
|
+
// Update rotation based on movement
|
|
993
|
+
if (Math.abs(player.vx) > 0.1 || Math.abs(player.vz) > 0.1) {
|
|
994
|
+
player.rotation = Math.atan2(player.vx, player.vz);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// Update mesh positions
|
|
998
|
+
setPosition(player.body, player.x, player.y, player.z);
|
|
999
|
+
setPosition(player.cockpit, player.x, player.y + 0.8, player.z + 0.2);
|
|
1000
|
+
setPosition(player.glow, player.x, player.y - 0.8, player.z);
|
|
1001
|
+
|
|
1002
|
+
setPosition(player.thrusters[0], player.x - 1.2, player.y - 0.2, player.z - 0.8);
|
|
1003
|
+
setPosition(player.thrusters[1], player.x + 1.2, player.y - 0.2, player.z - 0.8);
|
|
1004
|
+
|
|
1005
|
+
// Apply rotations
|
|
1006
|
+
setRotation(player.body, player.tilt * 0.3, player.rotation, player.tilt);
|
|
1007
|
+
setRotation(player.cockpit, 0, player.rotation, 0);
|
|
1008
|
+
|
|
1009
|
+
// Create thruster particles
|
|
1010
|
+
if (Math.abs(player.vx) > 5 || Math.abs(player.vz) > 5) {
|
|
1011
|
+
createThrusterParticles();
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// Boundary check
|
|
1015
|
+
const boundary = CITY_SIZE;
|
|
1016
|
+
if (Math.abs(player.x) > boundary) player.x = Math.sign(player.x) * boundary;
|
|
1017
|
+
if (Math.abs(player.z) > boundary) player.z = Math.sign(player.z) * boundary;
|
|
1018
|
+
if (player.y < 1) player.y = 1;
|
|
1019
|
+
if (player.y > 50) player.y = 50;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
function updateVehicles(dt) {
|
|
1023
|
+
vehicles.forEach(vehicle => {
|
|
1024
|
+
// AI behavior - move to random waypoints
|
|
1025
|
+
vehicle.nextWaypoint -= dt;
|
|
1026
|
+
if (vehicle.nextWaypoint <= 0) {
|
|
1027
|
+
vehicle.target.x = (Math.random() - 0.5) * CITY_SIZE * 0.8;
|
|
1028
|
+
vehicle.target.z = (Math.random() - 0.5) * CITY_SIZE * 0.8;
|
|
1029
|
+
vehicle.target.y = 1.5 + Math.random() * 8;
|
|
1030
|
+
vehicle.nextWaypoint = 3 + Math.random() * 5;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// Move towards target
|
|
1034
|
+
const dx = vehicle.target.x - vehicle.x;
|
|
1035
|
+
const dy = vehicle.target.y - vehicle.y;
|
|
1036
|
+
const dz = vehicle.target.z - vehicle.z;
|
|
1037
|
+
const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
1038
|
+
|
|
1039
|
+
if (distance > 1) {
|
|
1040
|
+
vehicle.vx += (dx / distance) * vehicle.speed * dt;
|
|
1041
|
+
vehicle.vy += (dy / distance) * vehicle.speed * dt;
|
|
1042
|
+
vehicle.vz += (dz / distance) * vehicle.speed * dt;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// Apply physics
|
|
1046
|
+
vehicle.x += vehicle.vx * dt;
|
|
1047
|
+
vehicle.y += vehicle.vy * dt;
|
|
1048
|
+
vehicle.z += vehicle.vz * dt;
|
|
1049
|
+
|
|
1050
|
+
// Damping
|
|
1051
|
+
vehicle.vx *= 0.95;
|
|
1052
|
+
vehicle.vy *= 0.98;
|
|
1053
|
+
vehicle.vz *= 0.95;
|
|
1054
|
+
|
|
1055
|
+
// Update mesh positions (body + both glow layers)
|
|
1056
|
+
setPosition(vehicle.body, vehicle.x, vehicle.y, vehicle.z);
|
|
1057
|
+
setPosition(vehicle.glow, vehicle.x, vehicle.y - 0.5, vehicle.z);
|
|
1058
|
+
setPosition(vehicle.glow2, vehicle.x, vehicle.y - 0.6, vehicle.z);
|
|
1059
|
+
|
|
1060
|
+
// Occasional thruster particles
|
|
1061
|
+
if (Math.random() < 0.1) {
|
|
1062
|
+
createVehicleThrusterParticles(vehicle.x, vehicle.y, vehicle.z);
|
|
1063
|
+
}
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
function updatePackets(dt) {
|
|
1068
|
+
for (let i = dataPackets.length - 1; i >= 0; i--) {
|
|
1069
|
+
let p = dataPackets[i];
|
|
1070
|
+
if (!p.active) continue;
|
|
1071
|
+
|
|
1072
|
+
// Rotate and hover
|
|
1073
|
+
p.offset += dt * 3;
|
|
1074
|
+
setRotation(p.mesh, p.offset, p.offset * 0.5, p.offset * 0.2);
|
|
1075
|
+
setPosition(p.mesh, p.x, p.y + Math.sin(p.offset) * 0.5, p.z);
|
|
1076
|
+
|
|
1077
|
+
// Collision with player
|
|
1078
|
+
const dx = player.x - p.x;
|
|
1079
|
+
const dy = player.y - p.y;
|
|
1080
|
+
const dz = player.z - p.z;
|
|
1081
|
+
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
1082
|
+
|
|
1083
|
+
if (dist < 4.0) {
|
|
1084
|
+
p.active = false;
|
|
1085
|
+
playerScore += 100;
|
|
1086
|
+
setScale(p.mesh, 0, 0, 0);
|
|
1087
|
+
sfx('coin');
|
|
1088
|
+
if (currentMission && currentMission.type === 'DATA_HEIST') {
|
|
1089
|
+
currentMission.progress++;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
function updateParticles(dt) {
|
|
1096
|
+
for (let i = particles.length - 1; i >= 0; i--) {
|
|
1097
|
+
const particle = particles[i];
|
|
1098
|
+
|
|
1099
|
+
// Physics
|
|
1100
|
+
particle.x += particle.vx * dt;
|
|
1101
|
+
particle.y += particle.vy * dt;
|
|
1102
|
+
particle.z += particle.vz * dt;
|
|
1103
|
+
|
|
1104
|
+
particle.life -= dt;
|
|
1105
|
+
|
|
1106
|
+
// Update position
|
|
1107
|
+
setPosition(particle.mesh, particle.x, particle.y, particle.z);
|
|
1108
|
+
|
|
1109
|
+
// Fade out
|
|
1110
|
+
const alpha = particle.life / particle.maxLife;
|
|
1111
|
+
setScale(particle.mesh, alpha);
|
|
1112
|
+
|
|
1113
|
+
// Remove dead particles
|
|
1114
|
+
if (particle.life <= 0) {
|
|
1115
|
+
destroyMesh(particle.mesh);
|
|
1116
|
+
particles.splice(i, 1);
|
|
1117
|
+
|
|
1118
|
+
// Respawn ambient particles
|
|
1119
|
+
if (particle.type === 'ambient') {
|
|
1120
|
+
createAmbientParticle();
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
function updateCityLights(dt) {
|
|
1127
|
+
// Update blinking lights
|
|
1128
|
+
cityLights.forEach(light => {
|
|
1129
|
+
light.blinkTime -= dt;
|
|
1130
|
+
if (light.blinkTime <= 0) {
|
|
1131
|
+
light.isOn = !light.isOn;
|
|
1132
|
+
light.blinkTime = 0.5 + Math.random() * 1.5;
|
|
1133
|
+
|
|
1134
|
+
// Change light color/visibility
|
|
1135
|
+
if (light.isOn) {
|
|
1136
|
+
setScale(light.mesh, 1);
|
|
1137
|
+
} else {
|
|
1138
|
+
setScale(light.mesh, 0.3);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
// Update building windows
|
|
1144
|
+
buildings.forEach(building => {
|
|
1145
|
+
building.windows.forEach(window => {
|
|
1146
|
+
window.flickerTime -= dt;
|
|
1147
|
+
if (window.flickerTime <= 0) {
|
|
1148
|
+
// 💡 Random flicker effect - future enhancement: change mesh color
|
|
1149
|
+
// const _isOn = Math.random() < 0.8; // Would toggle window brightness
|
|
1150
|
+
window.flickerTime = 0.5 + Math.random() * 3;
|
|
1151
|
+
}
|
|
1152
|
+
});
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
function updateCamera(dt) {
|
|
1157
|
+
// Smooth camera follow
|
|
1158
|
+
const followDistance = 15;
|
|
1159
|
+
const followHeight = 8;
|
|
1160
|
+
|
|
1161
|
+
camera.targetX = player.x - Math.sin(player.rotation) * followDistance;
|
|
1162
|
+
camera.targetY = player.y + followHeight;
|
|
1163
|
+
camera.targetZ = player.z - Math.cos(player.rotation) * followDistance;
|
|
1164
|
+
|
|
1165
|
+
// Smooth interpolation
|
|
1166
|
+
camera.x += (camera.targetX - camera.x) * 3 * dt;
|
|
1167
|
+
camera.y += (camera.targetY - camera.y) * 3 * dt;
|
|
1168
|
+
camera.z += (camera.targetZ - camera.z) * 3 * dt;
|
|
1169
|
+
|
|
1170
|
+
setCameraPosition(camera.x, camera.y, camera.z);
|
|
1171
|
+
setCameraTarget(player.x, player.y + 2, player.z);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
function updateNeonSigns(dt) {
|
|
1175
|
+
neonSigns.forEach(sign => {
|
|
1176
|
+
sign.pulsePhase += dt * 4;
|
|
1177
|
+
// ⚡ Future enhancement: Use Math.sin(sign.pulsePhase) to pulse glow intensity
|
|
1178
|
+
});
|
|
1179
|
+
|
|
1180
|
+
// Update floating platforms
|
|
1181
|
+
cityObjects.forEach(obj => {
|
|
1182
|
+
if (obj.type === 'platform') {
|
|
1183
|
+
const newY = 15 + Math.sin(gameTime * 0.5 + obj.index) * 3;
|
|
1184
|
+
setPosition(obj.mesh, Math.cos(obj.angle) * 40, newY, Math.sin(obj.angle) * 40);
|
|
1185
|
+
setPosition(obj.glow, Math.cos(obj.angle) * 40, newY - 0.5, Math.sin(obj.angle) * 40);
|
|
1186
|
+
}
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
function createBoostParticles() {
|
|
1191
|
+
// 💨 BRIGHTER BOOST PARTICLES - More visual impact!
|
|
1192
|
+
for (let i = 0; i < 30; i++) {
|
|
1193
|
+
const boostColors = [0x00ffff, 0xff00ff, 0xffff00, 0x00ff00];
|
|
1194
|
+
const particle = createSphere(0.25, boostColors[i % boostColors.length], [
|
|
1195
|
+
player.x + (Math.random() - 0.5) * 5,
|
|
1196
|
+
player.y + (Math.random() - 0.5) * 2.5,
|
|
1197
|
+
player.z + (Math.random() - 0.5) * 5,
|
|
1198
|
+
]);
|
|
1199
|
+
|
|
1200
|
+
particles.push({
|
|
1201
|
+
mesh: particle,
|
|
1202
|
+
x: player.x,
|
|
1203
|
+
y: player.y,
|
|
1204
|
+
z: player.z,
|
|
1205
|
+
vx: (Math.random() - 0.5) * 20,
|
|
1206
|
+
vy: (Math.random() - 0.5) * 8,
|
|
1207
|
+
vz: (Math.random() - 0.5) * 20,
|
|
1208
|
+
life: 2.0,
|
|
1209
|
+
maxLife: 2.0,
|
|
1210
|
+
type: 'boost',
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
function createThrusterParticles() {
|
|
1216
|
+
// 🔥 BRIGHTER THRUSTER TRAIL - Alternating colors
|
|
1217
|
+
const thrusterColors = [0xff6600, 0xffaa00, 0xff00ff, 0x00ffff];
|
|
1218
|
+
for (let i = 0; i < 3; i++) {
|
|
1219
|
+
const thrusterX = player.x + (i === 0 ? -1.2 : 1.2);
|
|
1220
|
+
const particle = createSphere(
|
|
1221
|
+
0.2,
|
|
1222
|
+
thrusterColors[Math.floor(Math.random() * thrusterColors.length)],
|
|
1223
|
+
[thrusterX, player.y - 0.5, player.z - 1]
|
|
1224
|
+
);
|
|
1225
|
+
|
|
1226
|
+
particles.push({
|
|
1227
|
+
mesh: particle,
|
|
1228
|
+
x: thrusterX,
|
|
1229
|
+
y: player.y - 0.5,
|
|
1230
|
+
z: player.z - 1,
|
|
1231
|
+
vx: (Math.random() - 0.5) * 4,
|
|
1232
|
+
vy: -Math.random() * 3,
|
|
1233
|
+
vz: Math.random() * 10 + 6,
|
|
1234
|
+
life: 1.0,
|
|
1235
|
+
maxLife: 1.0,
|
|
1236
|
+
type: 'thruster',
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
function createVehicleThrusterParticles(x, y, z) {
|
|
1242
|
+
const particle = createSphere(0.1, 0x4488ff, [x, y - 0.3, z - 0.8]);
|
|
1243
|
+
|
|
1244
|
+
particles.push({
|
|
1245
|
+
mesh: particle,
|
|
1246
|
+
x: x,
|
|
1247
|
+
y: y - 0.3,
|
|
1248
|
+
z: z - 0.8,
|
|
1249
|
+
vx: (Math.random() - 0.5) * 2,
|
|
1250
|
+
vy: -Math.random(),
|
|
1251
|
+
vz: Math.random() * 3 + 2,
|
|
1252
|
+
life: 0.5,
|
|
1253
|
+
maxLife: 0.5,
|
|
1254
|
+
type: 'vehicle',
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
function drawHUD() {
|
|
1259
|
+
// Health bar
|
|
1260
|
+
const hbx = 16,
|
|
1261
|
+
hby = 16,
|
|
1262
|
+
hbw = 150,
|
|
1263
|
+
hbh = 12;
|
|
1264
|
+
rect(hbx, hby, hbw, hbh, rgba8(40, 0, 0, 200), true);
|
|
1265
|
+
const hFrac = playerHealth / playerMaxHealth;
|
|
1266
|
+
if (hFrac > 0) rect(hbx, hby, Math.floor(hbw * hFrac), hbh, rgba8(255, 50, 50, 255), true);
|
|
1267
|
+
rect(hbx, hby, hbw, hbh, rgba8(255, 100, 100, 150), false);
|
|
1268
|
+
print(`HP ${playerHealth}/${playerMaxHealth}`, hbx + 4, hby + 2, rgba8(255, 255, 255, 255));
|
|
1269
|
+
|
|
1270
|
+
// Score and stats
|
|
1271
|
+
print(`CREDITS: ${playerScore}`, 480, 18, rgba8(255, 255, 100, 255));
|
|
1272
|
+
print(`DRONES: ${dronesDestroyed}`, 480, 34, rgba8(255, 150, 150, 255));
|
|
1273
|
+
|
|
1274
|
+
// Mode & speed
|
|
1275
|
+
const speedMag = Math.sqrt(player.vx * player.vx + player.vz * player.vz);
|
|
1276
|
+
print(
|
|
1277
|
+
`${flying ? 'FLIGHT' : 'HOVER'} ${speedMag.toFixed(0)}m/s`,
|
|
1278
|
+
16,
|
|
1279
|
+
34,
|
|
1280
|
+
rgba8(0, 255, 255, 255)
|
|
1281
|
+
);
|
|
1282
|
+
print(`ALT: ${player.y.toFixed(0)}m`, 16, 50, rgba8(100, 255, 100, 255));
|
|
1283
|
+
|
|
1284
|
+
// Mission panel
|
|
1285
|
+
if (currentMission) {
|
|
1286
|
+
const mpx = 200,
|
|
1287
|
+
mpy = 16,
|
|
1288
|
+
mpw = 240,
|
|
1289
|
+
mph = 50;
|
|
1290
|
+
rect(mpx, mpy, mpw, mph, rgba8(0, 0, 30, 200), true);
|
|
1291
|
+
rect(mpx, mpy, mpw, mph, rgba8(0, 200, 255, 150), false);
|
|
1292
|
+
print(currentMission.name, mpx + 6, mpy + 4, rgba8(0, 255, 255, 255));
|
|
1293
|
+
print(currentMission.desc, mpx + 6, mpy + 18, rgba8(200, 200, 255, 255));
|
|
1294
|
+
const tColor = missionTimer < 10 ? rgba8(255, 80, 80, 255) : rgba8(255, 255, 200, 255);
|
|
1295
|
+
print(
|
|
1296
|
+
`${currentMission.progress}/${currentMission.target} TIME: ${Math.ceil(missionTimer)}s`,
|
|
1297
|
+
mpx + 6,
|
|
1298
|
+
mpy + 34,
|
|
1299
|
+
tColor
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
// Mission message (center, fading)
|
|
1304
|
+
if (missionMsgTimer > 0) {
|
|
1305
|
+
const alpha = Math.min(1, missionMsgTimer) * 255;
|
|
1306
|
+
rect(160, 160, 320, 24, rgba8(0, 0, 0, Math.floor(alpha * 0.6)), true);
|
|
1307
|
+
print(missionMsg, 180, 166, rgba8(255, 255, 0, Math.floor(alpha)));
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// Damage flash
|
|
1311
|
+
if (playerDmgCD > 0.4) {
|
|
1312
|
+
rect(0, 0, 640, 360, rgba8(255, 0, 0, 60), true);
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
// Controls
|
|
1316
|
+
print('WASD:Move SHIFT:Fly SPACE:Boost(ram drones!)', 16, 344, rgba8(200, 200, 255, 180));
|
|
1317
|
+
|
|
1318
|
+
// Mini-map (radar)
|
|
1319
|
+
const radarSize = 80;
|
|
1320
|
+
const radarX = 560;
|
|
1321
|
+
const radarY = 260;
|
|
1322
|
+
rect(
|
|
1323
|
+
radarX - radarSize / 2,
|
|
1324
|
+
radarY - radarSize / 2,
|
|
1325
|
+
radarSize,
|
|
1326
|
+
radarSize,
|
|
1327
|
+
rgba8(0, 50, 0, 150),
|
|
1328
|
+
true
|
|
1329
|
+
);
|
|
1330
|
+
rect(
|
|
1331
|
+
radarX - radarSize / 2,
|
|
1332
|
+
radarY - radarSize / 2,
|
|
1333
|
+
radarSize,
|
|
1334
|
+
radarSize,
|
|
1335
|
+
rgba8(0, 255, 0, 100),
|
|
1336
|
+
false
|
|
1337
|
+
);
|
|
1338
|
+
rect(radarX - 1, radarY - 1, 2, 2, rgba8(255, 255, 255, 255), true);
|
|
1339
|
+
|
|
1340
|
+
// Vehicle dots (magenta)
|
|
1341
|
+
vehicles.forEach(v => {
|
|
1342
|
+
const rx = ((v.x - player.x) / CITY_SIZE) * radarSize * 0.4;
|
|
1343
|
+
const rz = ((v.z - player.z) / CITY_SIZE) * radarSize * 0.4;
|
|
1344
|
+
if (Math.abs(rx) < radarSize / 2 && Math.abs(rz) < radarSize / 2) {
|
|
1345
|
+
rect(radarX + rx - 1, radarY + rz - 1, 2, 2, rgba8(255, 0, 255, 200), true);
|
|
1346
|
+
}
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1349
|
+
// Drone dots (red)
|
|
1350
|
+
drones.forEach(d => {
|
|
1351
|
+
if (!d.alive) return;
|
|
1352
|
+
const rx = ((d.x - player.x) / CITY_SIZE) * radarSize * 0.4;
|
|
1353
|
+
const rz = ((d.z - player.z) / CITY_SIZE) * radarSize * 0.4;
|
|
1354
|
+
if (Math.abs(rx) < radarSize / 2 && Math.abs(rz) < radarSize / 2) {
|
|
1355
|
+
rect(radarX + rx - 1, radarY + rz - 1, 3, 3, rgba8(255, 50, 50, 255), true);
|
|
1356
|
+
}
|
|
1357
|
+
});
|
|
1358
|
+
|
|
1359
|
+
// Checkpoint dot (cyan, blinking)
|
|
1360
|
+
if (currentMission && currentMission.type === 'SPEED_RUN') {
|
|
1361
|
+
const rx = ((currentMission.cx - player.x) / CITY_SIZE) * radarSize * 0.4;
|
|
1362
|
+
const rz = ((currentMission.cz - player.z) / CITY_SIZE) * radarSize * 0.4;
|
|
1363
|
+
if (
|
|
1364
|
+
Math.abs(rx) < radarSize / 2 &&
|
|
1365
|
+
Math.abs(rz) < radarSize / 2 &&
|
|
1366
|
+
Math.sin(gameTime * 8) > 0
|
|
1367
|
+
) {
|
|
1368
|
+
rect(radarX + rx - 2, radarY + rz - 2, 4, 4, rgba8(0, 255, 255, 255), true);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
// Data packet dots (green)
|
|
1373
|
+
dataPackets.forEach(p => {
|
|
1374
|
+
if (!p.active) return;
|
|
1375
|
+
const rx = ((p.x - player.x) / CITY_SIZE) * radarSize * 0.4;
|
|
1376
|
+
const rz = ((p.z - player.z) / CITY_SIZE) * radarSize * 0.4;
|
|
1377
|
+
if (Math.abs(rx) < radarSize / 2 && Math.abs(rz) < radarSize / 2) {
|
|
1378
|
+
rect(radarX + rx, radarY + rz, 2, 2, rgba8(0, 255, 0, 200), true);
|
|
1379
|
+
}
|
|
1380
|
+
});
|
|
1381
|
+
|
|
1382
|
+
print('RADAR', radarX - 15, radarY + radarSize / 2 + 4, rgba8(0, 255, 0, 255));
|
|
1383
|
+
}
|