awacke1 commited on
Commit
662afab
·
verified ·
1 Parent(s): 995d988

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +236 -65
index.html CHANGED
@@ -54,11 +54,11 @@
54
  .sidebar button:hover {
55
  background: #45a049;
56
  }
57
- .sidebar .character-sheet, .sidebar .skills-sheet {
58
  margin-top: 20px;
59
  font-size: 14px;
60
  }
61
- .sidebar .character-sheet div, .sidebar .skills-sheet div {
62
  margin: 5px 0;
63
  }
64
  .gallery {
@@ -93,8 +93,9 @@
93
  </head>
94
  <body>
95
  <div class="ui-container" id="race-ui">
96
- <h2>Starship Circuit Commander</h2>
97
  <div id="time">Time: 0</div>
 
98
  <div id="status">Status: Active</div>
99
  </div>
100
  <div class="sidebar" id="sidebar">
@@ -176,6 +177,10 @@
176
  <div>Shot Count: <span id="skill-shot-count">0</span><div class="skill-meter"><div id="skill-shot-count-meter" style="width: 0%"></div></div></div>
177
  <div>Turn Speed: <span id="skill-turn-speed">0</span><div class="skill-meter"><div id="skill-turn-speed-meter" style="width: 0%"></div></div></div>
178
  </div>
 
 
 
 
179
  </div>
180
  <div class="gallery" id="gallery">
181
  <h2>Lake Minnetonka Gallery</h2>
@@ -191,10 +196,12 @@
191
  // Game state
192
  let gameState = 'racing';
193
  let raceTime = 0;
 
194
  let currentTrack = 'Phelps Island Drift';
195
  let lastGatePass = 0;
196
  let isJumping = false;
197
  let jumpCooldown = 0;
 
198
 
199
  // Scene setup
200
  const scene = new THREE.Scene();
@@ -262,10 +269,15 @@
262
  // Player ship
263
  let playerShip = null;
264
  let playerData = null;
265
- function createShip(typeIndex, isPlayer) {
266
  const type = shipTypes[typeIndex];
267
  const ship = new THREE.Group();
268
- const bodyGeometry = new THREE.BoxGeometry(type.scale.x, type.scale.y, type.scale.z);
 
 
 
 
 
269
  const bodyMaterial = new THREE.MeshStandardMaterial({
270
  color: isPlayer ? 0x00ff00 : 0xff0000,
271
  metalness: 0.8,
@@ -274,38 +286,43 @@
274
  const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
275
  ship.add(body);
276
  if (type.name !== 'Rocketman') {
277
- const thrusterGeometry = new THREE.CylinderGeometry(0.2, 0.2, 0.5, 8);
278
  const thrusterMaterial = new THREE.MeshStandardMaterial({ color: 0xaaaaaa });
279
  for (let i = 0; i < 4; i++) {
280
  const thruster = new THREE.Mesh(thrusterGeometry, thrusterMaterial);
281
  thruster.position.set(
282
- (i % 2 === 0 ? 0.5 : -0.5) * type.scale.x * 0.8,
283
  0,
284
- (i < 2 ? 0.5 : -0.5) * type.scale.z * 0.8
285
  );
286
  thruster.rotation.x = Math.PI / 2;
287
  ship.add(thruster);
288
  }
289
  } else {
290
- const jetpackGeometry = new THREE.CylinderGeometry(0.3, 0.3, 0.8, 8);
291
  const jetpack = new THREE.Mesh(jetpackGeometry, new THREE.MeshStandardMaterial({ color: 0xaaaaaa }));
292
- jetpack.position.set(0, -0.5, 0);
293
  ship.add(jetpack);
294
  }
295
  ship.position.y = 10;
296
  ship.castShadow = true;
 
297
  ship.userData = {
298
  type: type.name,
299
  maxSpeed: type.speed,
300
  acceleration: type.accel,
301
- thrusters: Array(4).fill(type.maxHP),
302
- body: { front: type.maxHP, back: type.maxHP, left: type.maxHP, right: type.maxHP },
303
  active: true,
304
  speed: 0,
305
  currentWaypoint: 0,
306
- lastGatePass: 0
 
 
 
307
  };
308
  scene.add(ship);
 
309
  return ship;
310
  }
311
 
@@ -314,10 +331,12 @@
314
  const typeIndex = Math.floor(Math.random() * shipTypes.length);
315
  playerShip = createShip(typeIndex, true);
316
  playerData = playerShip.userData;
317
- // Reset skills
318
  Object.keys(skills).forEach(skill => skills[skill].level = 0);
 
 
319
  updateCharacterSheet();
320
  updateSkillsSheet();
 
321
  }
322
 
323
  // AI opponents
@@ -333,20 +352,51 @@
333
  const startGate = new THREE.Mesh(gateGeometry, gateMaterial);
334
  scene.add(startGate);
335
 
336
- // Particle system for bullets
337
- const bullets = [];
338
- function createBullet(position, direction) {
339
- const geometry = new THREE.SphereGeometry(0.2, 8, 8);
340
- const material = new THREE.MeshBasicMaterial({ color: 0xffff00 });
341
- const bullet = new THREE.Mesh(geometry, material);
342
- bullet.position.copy(position);
343
- bullet.userData = {
344
- direction: direction.clone(),
345
- lifetime: 120,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
346
  damage: skills.shotPower.effect(skills.shotPower.level)
347
  };
348
- scene.add(bullet);
349
- bullets.push(bullet);
 
350
  }
351
 
352
  // Particle system for explosions
@@ -389,17 +439,25 @@
389
  }
390
 
391
  // Building and column generation
392
- function createBuilding(x, z, width, depth, height) {
393
- const geometry = new THREE.BoxGeometry(width, height, depth);
394
  const material = new THREE.MeshStandardMaterial({
395
  color: Math.random() * 0xffffff,
396
  roughness: 0.7,
397
  metalness: 0.3
398
  });
399
  const building = new THREE.Mesh(geometry, material);
400
- building.position.set(x, height / 2, z);
401
  building.castShadow = true;
402
  building.receiveShadow = true;
 
 
 
 
 
 
 
 
403
  return building;
404
  }
405
 
@@ -625,7 +683,7 @@
625
  'Miami Beach Boulevard': { segments: ['straight', 'right', 'straight', 'left', 'gap', 'straight'], length: 48, streetWidth: 30, buildingHeight: 40 },
626
  'Florida Keys Canal Cruise': { segments: ['straight', 'left', 'right', 'gap', 'left', 'straight'], length: 45, streetWidth: 28, buildingHeight: 35 },
627
  'Orlando Theme Park Tour': { segments: ['straight', 'up', 'right', 'down', 'left', 'gap', 'straight'], length: 48, streetWidth: 30, buildingHeight: 40 },
628
- 'Tampa Bay Finance District Drift': { segments: ['straight', 'right', 'left', 'straight', 'gap', 'right', 'straight'], length: 50, streetWidth: 32, buildingHeight: 45 },
629
  'Jacksonville River City Run': { segments: ['straight', 'left', 'right', 'gap', 'left', 'straight'], length: 45, streetWidth: 28, buildingHeight: 35 },
630
  'Fort Lauderdale Cruise Port Circuit': { segments: ['straight', 'straight', 'straight', 'gap', 'straight'], length: 55, streetWidth: 35, buildingHeight: 50 },
631
  'Key West Sunset Sprint': { segments: ['straight', 'left', 'up', 'right', 'down', 'gap', 'straight'], length: 48, streetWidth: 30, buildingHeight: 40 },
@@ -671,21 +729,50 @@
671
  // Shooting
672
  let canShoot = true;
673
  let shotCooldown = 200;
 
 
674
  document.addEventListener('mousedown', (event) => {
675
  if (event.button === 0 && canShoot && gameState === 'racing' && playerData.active) {
676
  canShoot = false;
677
  setTimeout(() => canShoot = true, shotCooldown);
678
- const direction = new THREE.Vector3();
679
- camera.getWorldDirection(direction);
680
- const position = playerShip.position.clone().add(direction.clone().multiplyScalar(2));
681
- const count = skills.shotCount.effect(skills.shotCount.level);
682
- for (let i = 0; i < count; i++) {
683
- const offset = new THREE.Vector3((Math.random() - 0.5) * 0.1, (Math.random() - 0.5) * 0.1, 0);
684
- createBullet(position.clone().add(offset), direction);
 
 
 
 
 
 
685
  }
686
  }
687
  });
688
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
689
  // Update character sheet
690
  function updateCharacterSheet() {
691
  document.getElementById('ship-type').textContent = `Type: ${playerData.type}`;
@@ -742,24 +829,81 @@
742
  createExplosion(hitPosition);
743
  if (ship === playerShip) {
744
  updateCharacterSheet();
 
745
  }
746
  checkShipStatus(ship);
747
  }
748
 
749
- function checkShipStatus(ship) {
750
  const totalHP = ship.userData.thrusters.reduce((a, b) => a + b, 0) +
751
  Object.values(ship.userData.body).reduce((a, b) => a + b, 0);
752
  if (totalHP <= 0) {
753
  ship.userData.active = false;
754
  ship.visible = false;
755
  createExplosion(ship.position);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
756
  if (ship === playerShip) {
757
  document.getElementById('status').textContent = 'Status: Destroyed';
 
758
  endRace();
759
  }
760
  }
761
  }
762
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
763
  // Pickup handling
764
  function updatePickups() {
765
  for (let i = pickups.length - 1; i >= 0; i--) {
@@ -769,6 +913,7 @@
769
  if (skill.level < skill.maxLevel) {
770
  skill.level++;
771
  updateSkillsSheet();
 
772
  }
773
  scene.remove(pickup);
774
  pickups.splice(i, 1);
@@ -776,6 +921,48 @@
776
  }
777
  }
778
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
779
  // Player movement
780
  let rotation = 0;
781
  let bankAngle = 0;
@@ -798,6 +985,7 @@
798
  isJumping = true;
799
  jumpHeight = 5;
800
  jumpCooldown = 60;
 
801
  }
802
  if (isJumping) {
803
  jumpHeight -= 0.2;
@@ -853,29 +1041,6 @@
853
  });
854
  }
855
 
856
- // Bullet update
857
- function updateBullets() {
858
- const raycaster = new THREE.Raycaster();
859
- for (let i = bullets.length - 1; i >= 0; i--) {
860
- const bullet = bullets[i];
861
- bullet.userData.lifetime--;
862
- if (bullet.userData.lifetime <= 0) {
863
- scene.remove(bullet);
864
- bullets.splice(i, 1);
865
- continue;
866
- }
867
- bullet.position.add(bullet.userData.direction.clone().multiplyScalar(1));
868
- raycaster.set(bullet.position, bullet.userData.direction);
869
- const intersects = raycaster.intersectObjects([...aiShips, playerShip].filter(s => s.userData.active));
870
- if (intersects.length > 0) {
871
- const hitShip = intersects[0].object.parent;
872
- applyDamage(hitShip, bullet.userData.damage, bullet.position);
873
- scene.remove(bullet);
874
- bullets.splice(i, 1);
875
- }
876
- }
877
- }
878
-
879
  // Explosion update
880
  const explosions = [];
881
  function updateExplosions() {
@@ -924,7 +1089,9 @@
924
  currentTrack = trackName;
925
  if (currentTrackGroup) scene.remove(currentTrackGroup);
926
  pickups.forEach(p => scene.remove(p));
 
927
  pickups.length = 0;
 
928
  const { trackGroup, waypoints: newWaypoints, startPosition } = generateTrack(trackConfigs[trackName]);
929
  currentTrackGroup = trackGroup;
930
  waypoints = newWaypoints;
@@ -948,21 +1115,24 @@
948
  startGate.position.y = 12;
949
  startGate.rotation.y = Math.atan2(waypoints[1].x - waypoints[0].x, waypoints[1].z - waypoints[0].z);
950
  raceTime = 0;
 
951
  document.getElementById('time').textContent = `Time: 0`;
 
952
  document.getElementById('status').textContent = `Status: Active`;
953
  updateCharacterSheet();
954
  updateSkillsSheet();
 
955
  }
956
 
957
  // Restart race
958
  function restartRace() {
959
  scene.remove(playerShip);
960
- bullets.forEach(b => scene.remove(b));
961
  explosions.forEach(e => scene.remove(e));
962
  pickups.forEach(p => scene.remove(p));
963
- bullets.length = 0;
964
  explosions.length = 0;
965
  pickups.length = 0;
 
966
  initPlayer();
967
  startRace(currentTrack);
968
  }
@@ -971,7 +1141,8 @@
971
  function endRace() {
972
  gameState = 'menu';
973
  const activeShips = aiShips.filter(s => s.userData.active).length + (playerData.active ? 1 : 0);
974
- alert(`Race Over! Ships Remaining: ${activeShips}, Time: ${raceTime.toFixed(2)}s`);
 
975
  }
976
 
977
  // Gallery
@@ -988,7 +1159,7 @@
988
  if (gameState === 'racing') {
989
  updatePlayer();
990
  updateAI();
991
- updateBullets();
992
  updateExplosions();
993
  raceTime += 1 / 60;
994
  document.getElementById('time').textContent = `Time: ${raceTime.toFixed(2)}`;
 
54
  .sidebar button:hover {
55
  background: #45a049;
56
  }
57
+ .sidebar .character-sheet, .sidebar .skills-sheet, .sidebar .leaderboard {
58
  margin-top: 20px;
59
  font-size: 14px;
60
  }
61
+ .sidebar .character-sheet div, .sidebar .skills-sheet div, .sidebar .leaderboard div {
62
  margin: 5px 0;
63
  }
64
  .gallery {
 
93
  </head>
94
  <body>
95
  <div class="ui-container" id="race-ui">
96
+ <h2>Starship Circuit Commander 🏁</h2>
97
  <div id="time">Time: 0</div>
98
+ <div id="score">Score: 0</div>
99
  <div id="status">Status: Active</div>
100
  </div>
101
  <div class="sidebar" id="sidebar">
 
177
  <div>Shot Count: <span id="skill-shot-count">0</span><div class="skill-meter"><div id="skill-shot-count-meter" style="width: 0%"></div></div></div>
178
  <div>Turn Speed: <span id="skill-turn-speed">0</span><div class="skill-meter"><div id="skill-turn-speed-meter" style="width: 0%"></div></div></div>
179
  </div>
180
+ <div class="leaderboard" id="leaderboard">
181
+ <h3>Leaderboard 📊</h3>
182
+ <div id="logDisplay">Loading...</div>
183
+ </div>
184
  </div>
185
  <div class="gallery" id="gallery">
186
  <h2>Lake Minnetonka Gallery</h2>
 
196
  // Game state
197
  let gameState = 'racing';
198
  let raceTime = 0;
199
+ let score = 0;
200
  let currentTrack = 'Phelps Island Drift';
201
  let lastGatePass = 0;
202
  let isJumping = false;
203
  let jumpCooldown = 0;
204
+ let playerName = prompt('Enter your name:') || 'Player';
205
 
206
  // Scene setup
207
  const scene = new THREE.Scene();
 
269
  // Player ship
270
  let playerShip = null;
271
  let playerData = null;
272
+ function createShip(typeIndex, isPlayer, scaleMultiplier = 1, splitCount = 0) {
273
  const type = shipTypes[typeIndex];
274
  const ship = new THREE.Group();
275
+ const scale = {
276
+ x: type.scale.x * scaleMultiplier,
277
+ y: type.scale.y * scaleMultiplier,
278
+ z: type.scale.z * scaleMultiplier
279
+ };
280
+ const bodyGeometry = new THREE.BoxGeometry(scale.x, scale.y, scale.z);
281
  const bodyMaterial = new THREE.MeshStandardMaterial({
282
  color: isPlayer ? 0x00ff00 : 0xff0000,
283
  metalness: 0.8,
 
286
  const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
287
  ship.add(body);
288
  if (type.name !== 'Rocketman') {
289
+ const thrusterGeometry = new THREE.CylinderGeometry(0.2 * scaleMultiplier, 0.2 * scaleMultiplier, 0.5 * scaleMultiplier, 8);
290
  const thrusterMaterial = new THREE.MeshStandardMaterial({ color: 0xaaaaaa });
291
  for (let i = 0; i < 4; i++) {
292
  const thruster = new THREE.Mesh(thrusterGeometry, thrusterMaterial);
293
  thruster.position.set(
294
+ (i % 2 === 0 ? 0.5 : -0.5) * scale.x * 0.8,
295
  0,
296
+ (i < 2 ? 0.5 : -0.5) * scale.z * 0.8
297
  );
298
  thruster.rotation.x = Math.PI / 2;
299
  ship.add(thruster);
300
  }
301
  } else {
302
+ const jetpackGeometry = new THREE.CylinderGeometry(0.3 * scaleMultiplier, 0.3 * scaleMultiplier, 0.8 * scaleMultiplier, 8);
303
  const jetpack = new THREE.Mesh(jetpackGeometry, new THREE.MeshStandardMaterial({ color: 0xaaaaaa }));
304
+ jetpack.position.set(0, -0.5 * scaleMultiplier, 0);
305
  ship.add(jetpack);
306
  }
307
  ship.position.y = 10;
308
  ship.castShadow = true;
309
+ const maxHP = type.maxHP * scaleMultiplier;
310
  ship.userData = {
311
  type: type.name,
312
  maxSpeed: type.speed,
313
  acceleration: type.accel,
314
+ thrusters: Array(4).fill(maxHP),
315
+ body: { front: maxHP, back: maxHP, left: maxHP, right: maxHP },
316
  active: true,
317
  speed: 0,
318
  currentWaypoint: 0,
319
+ lastGatePass: 0,
320
+ isPlayer: isPlayer,
321
+ splitCount: splitCount,
322
+ typeIndex: typeIndex
323
  };
324
  scene.add(ship);
325
+ if (!isPlayer) aiShips.push(ship);
326
  return ship;
327
  }
328
 
 
331
  const typeIndex = Math.floor(Math.random() * shipTypes.length);
332
  playerShip = createShip(typeIndex, true);
333
  playerData = playerShip.userData;
 
334
  Object.keys(skills).forEach(skill => skills[skill].level = 0);
335
+ score = 0;
336
+ document.getElementById('score').textContent = `Score: ${score}`;
337
  updateCharacterSheet();
338
  updateSkillsSheet();
339
+ updateLeaderboard();
340
  }
341
 
342
  // AI opponents
 
352
  const startGate = new THREE.Mesh(gateGeometry, gateMaterial);
353
  scene.add(startGate);
354
 
355
+ // Missile system
356
+ const missiles = [];
357
+ function createMissile(position, target) {
358
+ const geometry = new THREE.ConeGeometry(0.2, 0.5, 8);
359
+ const material = new THREE.MeshStandardMaterial({ color: 0xff0000 });
360
+ const missile = new THREE.Mesh(geometry, material);
361
+ missile.position.copy(position);
362
+ missile.rotation.x = Math.PI / 2;
363
+ missile.castShadow = true;
364
+
365
+ // Flame trail
366
+ const flameCount = 10;
367
+ const flameGeometry = new THREE.BufferGeometry();
368
+ const flamePositions = new Float32Array(flameCount * 3);
369
+ const flameColors = new Float32Array(flameCount * 3);
370
+ for (let i = 0; i < flameCount; i++) {
371
+ const i3 = i * 3;
372
+ flamePositions[i3] = 0;
373
+ flamePositions[i3 + 1] = 0;
374
+ flamePositions[i3 + 2] = 0;
375
+ flameColors[i3] = 1;
376
+ flameColors[i3 + 1] = 0;
377
+ flameColors[i3 + 2] = 0;
378
+ }
379
+ flameGeometry.setAttribute('position', new THREE.BufferAttribute(flamePositions, 3));
380
+ flameGeometry.setAttribute('color', new THREE.BufferAttribute(flameColors, 3));
381
+ const flameMaterial = new THREE.PointsMaterial({
382
+ size: 0.2,
383
+ vertexColors: true,
384
+ transparent: true,
385
+ opacity: 0.5
386
+ });
387
+ const flame = new THREE.Points(flameGeometry, flameMaterial);
388
+ flame.position.z = -0.3;
389
+ missile.add(flame);
390
+
391
+ missile.userData = {
392
+ target: target,
393
+ speed: 1,
394
+ lifetime: 300,
395
  damage: skills.shotPower.effect(skills.shotPower.level)
396
  };
397
+ scene.add(missile);
398
+ missiles.push(missile);
399
+ return missile;
400
  }
401
 
402
  // Particle system for explosions
 
439
  }
440
 
441
  // Building and column generation
442
+ function createBuilding(x, z, width, depth, height, scaleMultiplier = 1, splitCount = 0) {
443
+ const geometry = new THREE.BoxGeometry(width * scaleMultiplier, height * scaleMultiplier, depth * scaleMultiplier);
444
  const material = new THREE.MeshStandardMaterial({
445
  color: Math.random() * 0xffffff,
446
  roughness: 0.7,
447
  metalness: 0.3
448
  });
449
  const building = new THREE.Mesh(geometry, material);
450
+ building.position.set(x, (height * scaleMultiplier) / 2, z);
451
  building.castShadow = true;
452
  building.receiveShadow = true;
453
+ building.userData = {
454
+ splitCount: splitCount,
455
+ maxHP: 10 * scaleMultiplier,
456
+ currentHP: 10 * scaleMultiplier,
457
+ width: width,
458
+ depth: depth,
459
+ height: height
460
+ };
461
  return building;
462
  }
463
 
 
683
  'Miami Beach Boulevard': { segments: ['straight', 'right', 'straight', 'left', 'gap', 'straight'], length: 48, streetWidth: 30, buildingHeight: 40 },
684
  'Florida Keys Canal Cruise': { segments: ['straight', 'left', 'right', 'gap', 'left', 'straight'], length: 45, streetWidth: 28, buildingHeight: 35 },
685
  'Orlando Theme Park Tour': { segments: ['straight', 'up', 'right', 'down', 'left', 'gap', 'straight'], length: 48, streetWidth: 30, buildingHeight: 40 },
686
+ 'Tampa Bay Finance District Drift': { segments: ['straight', 'right', 'left', 'straight', 'gap', 'right', 'standard'], length: 50, streetWidth: 32, buildingHeight: 45 },
687
  'Jacksonville River City Run': { segments: ['straight', 'left', 'right', 'gap', 'left', 'straight'], length: 45, streetWidth: 28, buildingHeight: 35 },
688
  'Fort Lauderdale Cruise Port Circuit': { segments: ['straight', 'straight', 'straight', 'gap', 'straight'], length: 55, streetWidth: 35, buildingHeight: 50 },
689
  'Key West Sunset Sprint': { segments: ['straight', 'left', 'up', 'right', 'down', 'gap', 'straight'], length: 48, streetWidth: 30, buildingHeight: 40 },
 
729
  // Shooting
730
  let canShoot = true;
731
  let shotCooldown = 200;
732
+ const raycaster = new THREE.Raycaster();
733
+ const mouse = new THREE.Vector2();
734
  document.addEventListener('mousedown', (event) => {
735
  if (event.button === 0 && canShoot && gameState === 'racing' && playerData.active) {
736
  canShoot = false;
737
  setTimeout(() => canShoot = true, shotCooldown);
738
+ mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
739
+ mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
740
+ raycaster.setFromCamera(mouse, camera);
741
+ const targets = [...aiShips, ...(currentTrackGroup ? currentTrackGroup.children.filter(c => c.isMesh && c.geometry.type === 'BoxGeometry') : [])].filter(t => t !== playerShip && (!t.userData.isPlayer || !t.userData.active));
742
+ const intersects = raycaster.intersectObjects(targets);
743
+ if (intersects.length > 0) {
744
+ const target = intersects[0].object;
745
+ const position = playerShip.position.clone().add(new THREE.Vector3(0, 0, -2));
746
+ const count = skills.shotCount.effect(skills.shotCount.level);
747
+ for (let i = 0; i < count; i++) {
748
+ createMissile(position.clone().add(new THREE.Vector3((Math.random() - 0.5) * 0.5, (Math.random() - 0.5) * 0.5, 0)), target);
749
+ }
750
+ logAction(playerName, score, 'Shot Fired');
751
  }
752
  }
753
  });
754
 
755
+ // Leaderboard logging
756
+ function logAction(name, points, action) {
757
+ const logEntry = {
758
+ player: name,
759
+ score: points,
760
+ action: action,
761
+ timestamp: new Date().toISOString()
762
+ };
763
+ let logs = JSON.parse(localStorage.getItem('gameLogs') || '[]');
764
+ logs.push(logEntry);
765
+ localStorage.setItem('gameLogs', JSON.stringify(logs));
766
+ updateLeaderboard();
767
+ }
768
+
769
+ function updateLeaderboard() {
770
+ let logs = JSON.parse(localStorage.getItem('gameLogs') || '[]');
771
+ document.getElementById('logDisplay').innerHTML = logs.slice(-5).map(log =>
772
+ `${log.player} (${log.score} pts): ${log.action} at ${new Date(log.timestamp).toLocaleString()}`
773
+ ).join('<br>') || 'No history!';
774
+ }
775
+
776
  // Update character sheet
777
  function updateCharacterSheet() {
778
  document.getElementById('ship-type').textContent = `Type: ${playerData.type}`;
 
829
  createExplosion(hitPosition);
830
  if (ship === playerShip) {
831
  updateCharacterSheet();
832
+ logAction(playerName, score, 'Ship Damaged');
833
  }
834
  checkShipStatus(ship);
835
  }
836
 
837
+ function checkShipStatus(ship, hitPosition) {
838
  const totalHP = ship.userData.thrusters.reduce((a, b) => a + b, 0) +
839
  Object.values(ship.userData.body).reduce((a, b) => a + b, 0);
840
  if (totalHP <= 0) {
841
  ship.userData.active = false;
842
  ship.visible = false;
843
  createExplosion(ship.position);
844
+ if (ship.userData.splitCount < 3) {
845
+ const newScale = 0.5;
846
+ const offsets = [
847
+ new THREE.Vector3(1, 0, 1),
848
+ new THREE.Vector3(-1, 0, -1)
849
+ ];
850
+ offsets.forEach(offset => {
851
+ const newShip = createShip(
852
+ ship.userData.typeIndex,
853
+ false,
854
+ newScale,
855
+ ship.userData.splitCount + 1
856
+ );
857
+ newShip.position.copy(ship.position).add(offset.multiplyScalar(2));
858
+ newShip.userData.currentWaypoint = ship.userData.currentWaypoint;
859
+ });
860
+ }
861
+ if (!ship.userData.isPlayer) {
862
+ const index = aiShips.indexOf(ship);
863
+ if (index > -1) aiShips.splice(index, 1);
864
+ score += 100;
865
+ document.getElementById('score').textContent = `Score: ${score}`;
866
+ logAction(playerName, score, 'Enemy Destroyed');
867
+ }
868
  if (ship === playerShip) {
869
  document.getElementById('status').textContent = 'Status: Destroyed';
870
+ logAction(playerName, score, 'Player Destroyed');
871
  endRace();
872
  }
873
  }
874
  }
875
 
876
+ // Building damage
877
+ function applyBuildingDamage(building, damage, hitPosition) {
878
+ building.userData.currentHP -= damage;
879
+ createExplosion(hitPosition);
880
+ if (building.userData.currentHP <= 0) {
881
+ currentTrackGroup.remove(building);
882
+ if (building.userData.splitCount < 3) {
883
+ const newScale = 0.5;
884
+ const offsets = [
885
+ new THREE.Vector3(5, 0, 5),
886
+ new THREE.Vector3(-5, 0, -5)
887
+ ];
888
+ offsets.forEach(offset => {
889
+ const newBuilding = createBuilding(
890
+ building.position.x + offset.x,
891
+ building.position.z + offset.z,
892
+ building.userData.width,
893
+ building.userData.depth,
894
+ building.userData.height,
895
+ newScale,
896
+ building.userData.splitCount + 1
897
+ );
898
+ currentTrackGroup.add(newBuilding);
899
+ });
900
+ }
901
+ score += 100;
902
+ document.getElementById('score').textContent = `Score: ${score}`;
903
+ logAction(playerName, score, 'Building Destroyed');
904
+ }
905
+ }
906
+
907
  // Pickup handling
908
  function updatePickups() {
909
  for (let i = pickups.length - 1; i >= 0; i--) {
 
913
  if (skill.level < skill.maxLevel) {
914
  skill.level++;
915
  updateSkillsSheet();
916
+ logAction(playerName, score, `Picked ${pickup.userData.skill}`);
917
  }
918
  scene.remove(pickup);
919
  pickups.splice(i, 1);
 
921
  }
922
  }
923
 
924
+ // Missile update
925
+ function updateMissiles() {
926
+ for (let i = missiles.length - 1; i >= 0; i--) {
927
+ const missile = missiles[i];
928
+ missile.userData.lifetime--;
929
+ if (missile.userData.lifetime <= 0 || !missile.userData.target || (!missile.userData.target.userData?.active && !missile.userData.target.isMesh)) {
930
+ scene.remove(missile);
931
+ missiles.splice(i, 1);
932
+ continue;
933
+ }
934
+ const targetPos = missile.userData.target.isMesh ? missile.userData.target.position : missile.userData.target.position;
935
+ const direction = targetPos.clone().sub(missile.position).normalize();
936
+ missile.position.add(direction.multiplyScalar(missile.userData.speed));
937
+ missile.lookAt(targetPos);
938
+ missile.rotation.x += Math.PI / 2;
939
+
940
+ // Update flame trail
941
+ const flame = missile.children[0];
942
+ const positions = flame.geometry.attributes.position.array;
943
+ for (let j = 0; j < positions.length / 3; j++) {
944
+ const j3 = j * 3;
945
+ positions[j3] = (Math.random() - 0.5) * 0.1;
946
+ positions[j3 + 1] = (Math.random() - 0.5) * 0.1;
947
+ positions[j3 + 2] = -0.3 - Math.random() * 0.2;
948
+ }
949
+ flame.geometry.attributes.position.needsUpdate = true;
950
+
951
+ // Check collision
952
+ const missileBox = new THREE.Box3().setFromObject(missile);
953
+ const targetBox = new THREE.Box3().setFromObject(missile.userData.target);
954
+ if (missileBox.intersectsBox(targetBox)) {
955
+ if (missile.userData.target.isMesh && missile.userData.target.geometry.type === 'BoxGeometry') {
956
+ applyBuildingDamage(missile.userData.target, missile.userData.damage, missile.position);
957
+ } else {
958
+ applyDamage(missile.userData.target, missile.userData.damage, missile.position);
959
+ }
960
+ scene.remove(missile);
961
+ missiles.splice(i, 1);
962
+ }
963
+ }
964
+ }
965
+
966
  // Player movement
967
  let rotation = 0;
968
  let bankAngle = 0;
 
985
  isJumping = true;
986
  jumpHeight = 5;
987
  jumpCooldown = 60;
988
+ logAction(playerName, score, 'Jumped');
989
  }
990
  if (isJumping) {
991
  jumpHeight -= 0.2;
 
1041
  });
1042
  }
1043
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1044
  // Explosion update
1045
  const explosions = [];
1046
  function updateExplosions() {
 
1089
  currentTrack = trackName;
1090
  if (currentTrackGroup) scene.remove(currentTrackGroup);
1091
  pickups.forEach(p => scene.remove(p));
1092
+ missiles.forEach(m => scene.remove(m));
1093
  pickups.length = 0;
1094
+ missiles.length = 0;
1095
  const { trackGroup, waypoints: newWaypoints, startPosition } = generateTrack(trackConfigs[trackName]);
1096
  currentTrackGroup = trackGroup;
1097
  waypoints = newWaypoints;
 
1115
  startGate.position.y = 12;
1116
  startGate.rotation.y = Math.atan2(waypoints[1].x - waypoints[0].x, waypoints[1].z - waypoints[0].z);
1117
  raceTime = 0;
1118
+ score = 0;
1119
  document.getElementById('time').textContent = `Time: 0`;
1120
+ document.getElementById('score').textContent = `Score: ${score}`;
1121
  document.getElementById('status').textContent = `Status: Active`;
1122
  updateCharacterSheet();
1123
  updateSkillsSheet();
1124
+ logAction(playerName, score, `Started ${trackName}`);
1125
  }
1126
 
1127
  // Restart race
1128
  function restartRace() {
1129
  scene.remove(playerShip);
 
1130
  explosions.forEach(e => scene.remove(e));
1131
  pickups.forEach(p => scene.remove(p));
1132
+ missiles.forEach(m => scene.remove(m));
1133
  explosions.length = 0;
1134
  pickups.length = 0;
1135
+ missiles.length = 0;
1136
  initPlayer();
1137
  startRace(currentTrack);
1138
  }
 
1141
  function endRace() {
1142
  gameState = 'menu';
1143
  const activeShips = aiShips.filter(s => s.userData.active).length + (playerData.active ? 1 : 0);
1144
+ alert(`Race Over! Ships Remaining: ${activeShips}, Time: ${raceTime.toFixed(2)}s, Score: ${score}`);
1145
+ logAction(playerName, score, 'Race Ended');
1146
  }
1147
 
1148
  // Gallery
 
1159
  if (gameState === 'racing') {
1160
  updatePlayer();
1161
  updateAI();
1162
+ updateMissiles();
1163
  updateExplosions();
1164
  raceTime += 1 / 60;
1165
  document.getElementById('time').textContent = `Time: ${raceTime.toFixed(2)}`;