awacke1 commited on
Commit
09eca26
·
verified ·
1 Parent(s): 6e7a654

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +503 -205
index.html CHANGED
@@ -121,7 +121,7 @@
121
  border: 2px solid #8B4513;
122
  }
123
 
124
- .sailing-controls {
125
  position: absolute;
126
  bottom: 140px;
127
  left: 20px;
@@ -142,10 +142,24 @@
142
  padding: 10px;
143
  border-radius: 50%;
144
  border: 2px solid #87CEEB;
145
- width: 60px;
146
- height: 60px;
147
  text-align: center;
148
- font-size: 24px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  }
150
 
151
  .health-bar {
@@ -192,17 +206,99 @@
192
  font-size: 16px;
193
  margin-top: 20px;
194
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  </style>
196
  </head>
197
  <body>
198
  <div class="ui-container">
199
  <div class="controls ui-panel">
200
- <h3>🏴‍☠️ Captain's Orders</h3>
201
- <p>⚓ WASD: Sail your ship</p>
202
- <p>🌊 Space: Adjust sails</p>
203
  <p>🎯 Click: Place towers</p>
204
- <p>🔄 R: Rotate ship</p>
205
- <p>⚡ Tab: Quick towers</p>
206
  </div>
207
 
208
  <div class="resource-panel ui-panel">
@@ -211,10 +307,26 @@
211
  <p>🏴‍☠️ Lives: <span id="lives">3</span></p>
212
  <p>⚔️ Wave: <span id="wave">1</span></p>
213
  <p>🎯 Score: <span id="score">0</span></p>
 
 
 
 
 
 
 
 
214
  </div>
215
 
216
  <div class="wind-indicator ui-panel">
217
- <div id="wind-arrow">💨</div>
 
 
 
 
 
 
 
 
218
  </div>
219
 
220
  <div class="tower-shop ui-panel">
@@ -232,11 +344,11 @@
232
  </button>
233
  </div>
234
 
235
- <div class="sailing-controls ui-panel">
236
- <h4>⛵ Sailing</h4>
237
- <p>Wind Speed: <span id="wind-speed">5</span> kts</p>
238
  <p>Ship Speed: <span id="ship-speed">0</span> kts</p>
239
- <p>Heading: <span id="heading">N</span></p>
 
240
  </div>
241
 
242
  <div class="wave-info ui-panel">
@@ -246,6 +358,15 @@
246
  <button id="start-wave" class="tower-button">Start Wave</button>
247
  </div>
248
 
 
 
 
 
 
 
 
 
 
249
  <div class="health-bar ui-panel">
250
  <div class="health-fill" id="health-fill" style="width: 100%"></div>
251
  </div>
@@ -274,7 +395,70 @@
274
  projectiles: [],
275
  particles: [],
276
  windDirection: 0,
277
- windSpeed: 5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
  };
279
 
280
  // Scene setup
@@ -287,14 +471,13 @@
287
  document.body.appendChild(renderer.domElement);
288
 
289
  // Camera controls
290
- let cameraOffset = new THREE.Vector3(0, 15, 20);
291
 
292
  // Player ship
293
  let playerShip;
294
  let shipRotation = 0;
295
- let shipSpeed = 0;
296
- let sailsOut = 0.5;
297
  const keys = {};
 
298
 
299
  // Atmospheric fog
300
  scene.fog = new THREE.Fog(0x2d1810, 50, 1000);
@@ -340,22 +523,33 @@
340
  const lakeGeometry = new THREE.PlaneGeometry(2000, 2000, 200, 200);
341
  const lakeMaterial = new THREE.ShaderMaterial({
342
  uniforms: {
343
- time: { value: 0 }
 
344
  },
345
  vertexShader: `
346
  uniform float time;
 
347
  varying vec2 vUv;
348
  varying vec3 vPosition;
 
349
 
350
  void main() {
351
  vUv = uv;
352
  vec3 pos = position;
353
 
354
- // Multiple wave patterns for realistic lake movement
 
 
 
 
355
  float wave1 = sin(pos.x * 0.02 + time * 0.5) * 0.2;
356
  float wave2 = sin(pos.y * 0.015 + time * 0.7) * 0.15;
357
  float wave3 = sin((pos.x + pos.y) * 0.01 + time * 0.3) * 0.25;
358
- pos.z = wave1 + wave2 + wave3;
 
 
 
 
359
 
360
  vPosition = pos;
361
  gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
@@ -363,17 +557,26 @@
363
  `,
364
  fragmentShader: `
365
  uniform float time;
 
366
  varying vec2 vUv;
367
  varying vec3 vPosition;
 
368
 
369
  void main() {
370
  vec3 deepWater = vec3(0.1, 0.3, 0.5);
371
  vec3 shallowWater = vec3(0.3, 0.5, 0.7);
 
 
 
 
372
 
373
  float wave = sin(vPosition.x * 0.1 + time) * sin(vPosition.y * 0.08 + time);
374
- vec3 waterColor = mix(deepWater, shallowWater, wave * 0.5 + 0.5);
375
 
376
- gl_FragColor = vec4(waterColor, 0.8);
 
 
 
377
  }
378
  `,
379
  transparent: true
@@ -382,37 +585,78 @@
382
  const lake = new THREE.Mesh(lakeGeometry, lakeMaterial);
383
  scene.add(lake);
384
 
385
- // Create multiple lake sections representing different areas of Minnetonka
386
- function createLakeSection(x, z, size, name) {
387
- const sectionGeometry = new THREE.RingGeometry(size * 0.8, size, 32);
388
  const sectionMaterial = new THREE.MeshBasicMaterial({
389
- color: 0x87CEEB,
390
  transparent: true,
391
- opacity: 0.1,
392
  side: THREE.DoubleSide
393
  });
394
  const section = new THREE.Mesh(sectionGeometry, sectionMaterial);
395
  section.rotation.x = -Math.PI / 2;
396
- section.position.set(x, 0.1, z);
397
- section.userData = { name: name };
398
  scene.add(section);
399
- }
400
-
401
- // Create the different bays of Lake Minnetonka
402
- createLakeSection(0, 0, 50, "Upper Lake");
403
- createLakeSection(-80, -60, 40, "Wayzata Bay");
404
- createLakeSection(70, -50, 35, "Crystal Bay");
405
- createLakeSection(-50, 80, 30, "Carmans Bay");
406
- createLakeSection(60, 70, 32, "Smithtown Bay");
407
 
408
  // Create islands and shoreline features
409
- function createIsland(x, z, size) {
410
  const islandGeometry = new THREE.CylinderGeometry(size, size * 1.2, 2, 16);
411
  const islandMaterial = new THREE.MeshLambertMaterial({ color: 0x4d5d3d });
412
  const island = new THREE.Mesh(islandGeometry, islandMaterial);
413
  island.position.set(x, 0, z);
414
  island.castShadow = true;
415
- island.userData = { type: 'buildable' };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
  scene.add(island);
417
 
418
  // Add some trees
@@ -431,15 +675,35 @@
431
  return island;
432
  }
433
 
434
- // Create strategic islands for tower placement
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
435
  const islands = [
436
- createIsland(-20, -30, 8),
437
- createIsland(30, -20, 6),
438
- createIsland(-40, 20, 7),
439
- createIsland(25, 35, 5),
440
- createIsland(0, -50, 9),
441
- createIsland(-60, -10, 6),
442
- createIsland(45, 10, 7)
 
 
 
 
443
  ];
444
 
445
  // Create player ship
@@ -447,21 +711,28 @@
447
  const shipGroup = new THREE.Group();
448
 
449
  // Hull
450
- const hullGeometry = new THREE.BoxGeometry(2, 0.5, 6);
451
  const hullMaterial = new THREE.MeshLambertMaterial({ color: 0x8B4513 });
452
  const hull = new THREE.Mesh(hullGeometry, hullMaterial);
453
- hull.position.y = 0.25;
454
  shipGroup.add(hull);
455
 
 
 
 
 
 
 
 
456
  // Mast
457
- const mastGeometry = new THREE.CylinderGeometry(0.1, 0.1, 8);
458
  const mastMaterial = new THREE.MeshLambertMaterial({ color: 0x654321 });
459
  const mast = new THREE.Mesh(mastGeometry, mastMaterial);
460
- mast.position.y = 4;
461
  shipGroup.add(mast);
462
 
463
- // Sail
464
- const sailGeometry = new THREE.PlaneGeometry(3, 5);
465
  const sailMaterial = new THREE.MeshLambertMaterial({
466
  color: 0xffffff,
467
  transparent: true,
@@ -469,10 +740,18 @@
469
  side: THREE.DoubleSide
470
  });
471
  const sail = new THREE.Mesh(sailGeometry, sailMaterial);
472
- sail.position.set(1.5, 4, 0);
473
  sail.userData = { type: 'sail' };
474
  shipGroup.add(sail);
475
 
 
 
 
 
 
 
 
 
476
  shipGroup.position.set(0, 1, 0);
477
  return shipGroup;
478
  }
@@ -480,18 +759,16 @@
480
  playerShip = createPlayerShip();
481
  scene.add(playerShip);
482
 
483
- // Enemy ship creation
484
  function createEnemyShip() {
485
  const shipGroup = new THREE.Group();
486
 
487
- // Hull (smaller and darker)
488
  const hullGeometry = new THREE.BoxGeometry(1.5, 0.4, 4);
489
  const hullMaterial = new THREE.MeshLambertMaterial({ color: 0x2c1810 });
490
  const hull = new THREE.Mesh(hullGeometry, hullMaterial);
491
  hull.position.y = 0.2;
492
  shipGroup.add(hull);
493
 
494
- // Pirate flag
495
  const flagGeometry = new THREE.PlaneGeometry(1, 1);
496
  const flagMaterial = new THREE.MeshLambertMaterial({
497
  color: 0x000000,
@@ -505,17 +782,15 @@
505
  return shipGroup;
506
  }
507
 
508
- // Tower creation functions
509
  function createCannonTower(position) {
510
  const towerGroup = new THREE.Group();
511
 
512
- // Base
513
  const baseGeometry = new THREE.CylinderGeometry(1.5, 2, 2);
514
  const baseMaterial = new THREE.MeshLambertMaterial({ color: 0x654321 });
515
  const base = new THREE.Mesh(baseGeometry, baseMaterial);
516
  towerGroup.add(base);
517
 
518
- // Cannon
519
  const cannonGeometry = new THREE.CylinderGeometry(0.2, 0.3, 2);
520
  const cannonMaterial = new THREE.MeshLambertMaterial({ color: 0x2c2c2c });
521
  const cannon = new THREE.Mesh(cannonGeometry, cannonMaterial);
@@ -539,13 +814,11 @@
539
  function createHarpoonTower(position) {
540
  const towerGroup = new THREE.Group();
541
 
542
- // Base
543
  const baseGeometry = new THREE.BoxGeometry(2, 1, 2);
544
  const baseMaterial = new THREE.MeshLambertMaterial({ color: 0x8B7355 });
545
  const base = new THREE.Mesh(baseGeometry, baseMaterial);
546
  towerGroup.add(base);
547
 
548
- // Harpoon launcher
549
  const launcherGeometry = new THREE.BoxGeometry(0.5, 0.5, 3);
550
  const launcherMaterial = new THREE.MeshLambertMaterial({ color: 0x2c2c2c });
551
  const launcher = new THREE.Mesh(launcherGeometry, launcherMaterial);
@@ -568,13 +841,11 @@
568
  function createNetTower(position) {
569
  const towerGroup = new THREE.Group();
570
 
571
- // Base
572
  const baseGeometry = new THREE.CylinderGeometry(1, 1.5, 1.5);
573
  const baseMaterial = new THREE.MeshLambertMaterial({ color: 0x4d5d3d });
574
  const base = new THREE.Mesh(baseGeometry, baseMaterial);
575
  towerGroup.add(base);
576
 
577
- // Net mechanism
578
  const netGeometry = new THREE.TorusGeometry(1, 0.1, 8, 16);
579
  const netMaterial = new THREE.MeshLambertMaterial({ color: 0x8B8B00 });
580
  const net = new THREE.Mesh(netGeometry, netMaterial);
@@ -598,21 +869,18 @@
598
  function createLighthouseTower(position) {
599
  const towerGroup = new THREE.Group();
600
 
601
- // Base
602
  const baseGeometry = new THREE.CylinderGeometry(1.5, 2, 8);
603
  const baseMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
604
  const base = new THREE.Mesh(baseGeometry, baseMaterial);
605
  base.position.y = 4;
606
  towerGroup.add(base);
607
 
608
- // Light
609
  const lightGeometry = new THREE.SphereGeometry(0.5);
610
  const lightMaterial = new THREE.MeshBasicMaterial({ color: 0xFFFF00 });
611
  const light = new THREE.Mesh(lightGeometry, lightMaterial);
612
  light.position.y = 8.5;
613
  towerGroup.add(light);
614
 
615
- // Area light effect
616
  const areaLight = new THREE.PointLight(0xFFFF00, 1, 60);
617
  areaLight.position.y = 8.5;
618
  towerGroup.add(areaLight);
@@ -631,7 +899,169 @@
631
  return towerGroup;
632
  }
633
 
634
- // Projectile creation
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
635
  function createProjectile(start, target, type) {
636
  let projectileGeometry, projectileMaterial;
637
 
@@ -675,7 +1105,6 @@
675
  return projectile;
676
  }
677
 
678
- // Enemy spawning
679
  function spawnEnemyWave() {
680
  const waveSize = gameState.wave * 3 + 2;
681
  const spawnPoints = [
@@ -710,14 +1139,12 @@
710
  }
711
  }
712
 
713
- // Combat system
714
  function updateTowers() {
715
  const currentTime = Date.now();
716
 
717
  gameState.towers.forEach(tower => {
718
  if (currentTime - tower.userData.lastFire < tower.userData.fireRate) return;
719
 
720
- // Find nearest enemy in range
721
  let nearestEnemy = null;
722
  let nearestDistance = Infinity;
723
 
@@ -732,7 +1159,6 @@
732
  if (nearestEnemy) {
733
  tower.userData.lastFire = currentTime;
734
 
735
- // Create projectile
736
  const projectileType = {
737
  'cannon': 'cannonball',
738
  'harpoon': 'harpoon',
@@ -747,7 +1173,6 @@
747
  gameState.projectiles.push(projectile);
748
  scene.add(projectile);
749
 
750
- // Point tower at target
751
  tower.lookAt(nearestEnemy.position);
752
  }
753
  });
@@ -763,14 +1188,11 @@
763
  return;
764
  }
765
 
766
- // Move projectile
767
  const movement = projectile.userData.direction.clone().multiplyScalar(projectile.userData.speed);
768
  projectile.position.add(movement);
769
 
770
- // Check collision with enemies
771
  gameState.enemies.forEach((enemy, enemyIndex) => {
772
  if (projectile.position.distanceTo(enemy.position) < 2) {
773
- // Hit!
774
  const tower = gameState.towers.find(t =>
775
  t.userData.type === projectile.userData.type.replace('ball', '').replace('lighthouse', 'lighthouse')
776
  );
@@ -778,13 +1200,11 @@
778
  if (tower) {
779
  enemy.userData.health -= tower.userData.damage;
780
 
781
- // Special effects
782
  if (tower.userData.type === 'net') {
783
  enemy.userData.slowFactor = tower.userData.slow;
784
  enemy.userData.slowTime = 3000;
785
  }
786
 
787
- // Create hit particle effect
788
  createHitEffect(enemy.position);
789
  }
790
 
@@ -797,7 +1217,6 @@
797
 
798
  function updateEnemies() {
799
  gameState.enemies.forEach((enemy, index) => {
800
- // Remove dead enemies
801
  if (enemy.userData.health <= 0) {
802
  gameState.gold += enemy.userData.value;
803
  gameState.score += enemy.userData.value;
@@ -806,7 +1225,6 @@
806
  return;
807
  }
808
 
809
- // Update slow effect
810
  if (enemy.userData.slowTime > 0) {
811
  enemy.userData.slowTime -= 16;
812
  if (enemy.userData.slowTime <= 0) {
@@ -814,13 +1232,11 @@
814
  }
815
  }
816
 
817
- // Move towards player ship
818
  const direction = new THREE.Vector3().subVectors(playerShip.position, enemy.position).normalize();
819
  const speed = enemy.userData.speed * enemy.userData.slowFactor;
820
  enemy.position.add(direction.multiplyScalar(speed));
821
  enemy.lookAt(playerShip.position);
822
 
823
- // Check if reached player
824
  if (enemy.position.distanceTo(playerShip.position) < 3) {
825
  gameState.health -= 20;
826
  gameState.lives--;
@@ -834,7 +1250,6 @@
834
  });
835
  }
836
 
837
- // Visual effects
838
  function createHitEffect(position) {
839
  for (let i = 0; i < 10; i++) {
840
  const particleGeometry = new THREE.SphereGeometry(0.1);
@@ -869,84 +1284,11 @@
869
  }
870
 
871
  particle.position.add(particle.userData.velocity);
872
- particle.userData.velocity.y -= 0.005; // Gravity
873
  particle.material.opacity = particle.userData.life / 60;
874
  });
875
  }
876
 
877
- // Sailing physics
878
- function updateShipPhysics() {
879
- // Wind effect on sailing
880
- const windAngle = gameState.windDirection;
881
- const windEffect = Math.cos(shipRotation - windAngle) * gameState.windSpeed * sailsOut;
882
-
883
- // Calculate ship speed based on wind
884
- shipSpeed = Math.max(0, windEffect * 0.1);
885
-
886
- // Move ship
887
- const movement = new THREE.Vector3(
888
- Math.sin(shipRotation) * shipSpeed,
889
- 0,
890
- Math.cos(shipRotation) * shipSpeed
891
- );
892
-
893
- playerShip.position.add(movement);
894
-
895
- // Constrain to lake bounds
896
- playerShip.position.x = Math.max(-90, Math.min(90, playerShip.position.x));
897
- playerShip.position.z = Math.max(-90, Math.min(90, playerShip.position.z));
898
-
899
- // Update sail animation based on wind
900
- const sail = playerShip.children.find(child => child.userData.type === 'sail');
901
- if (sail) {
902
- sail.rotation.y = Math.sin(Date.now() * 0.003) * 0.1 * sailsOut;
903
- }
904
- }
905
-
906
- // Input handling
907
- document.addEventListener('keydown', (event) => {
908
- keys[event.code] = true;
909
-
910
- if (event.code === 'Space') {
911
- event.preventDefault();
912
- sailsOut = sailsOut === 1 ? 0.3 : 1;
913
- }
914
-
915
- if (event.code === 'KeyR') {
916
- shipRotation += Math.PI / 4;
917
- }
918
-
919
- // Quick tower selection
920
- if (event.code === 'Tab') {
921
- event.preventDefault();
922
- cycleTowerSelection();
923
- }
924
- });
925
-
926
- document.addEventListener('keyup', (event) => {
927
- keys[event.code] = false;
928
- });
929
-
930
- // Ship movement
931
- function updateShipMovement() {
932
- const turnSpeed = 0.05;
933
- const acceleration = 0.01;
934
-
935
- if (keys['KeyA']) shipRotation -= turnSpeed;
936
- if (keys['KeyD']) shipRotation += turnSpeed;
937
- if (keys['KeyW']) sailsOut = Math.min(1, sailsOut + acceleration);
938
- if (keys['KeyS']) sailsOut = Math.max(0, sailsOut - acceleration);
939
-
940
- playerShip.rotation.y = shipRotation;
941
- }
942
-
943
- // Camera following
944
- function updateCamera() {
945
- const targetPosition = playerShip.position.clone().add(cameraOffset);
946
- camera.position.lerp(targetPosition, 0.05);
947
- camera.lookAt(playerShip.position);
948
- }
949
-
950
  // Tower placement
951
  let raycaster = new THREE.Raycaster();
952
  let mouse = new THREE.Vector2();
@@ -1016,7 +1358,6 @@
1016
  }
1017
  });
1018
 
1019
- // Game management
1020
  function startWave() {
1021
  gameState.waveActive = true;
1022
  spawnEnemyWave();
@@ -1033,33 +1374,6 @@
1033
  }
1034
  }
1035
 
1036
- function updateUI() {
1037
- document.getElementById('gold').textContent = gameState.gold;
1038
- document.getElementById('lives').textContent = gameState.lives;
1039
- document.getElementById('wave').textContent = gameState.wave;
1040
- document.getElementById('score').textContent = gameState.score;
1041
- document.getElementById('enemies-left').textContent = gameState.enemies.length;
1042
- document.getElementById('health-fill').style.width = `${gameState.health}%`;
1043
- document.getElementById('wind-speed').textContent = gameState.windSpeed;
1044
- document.getElementById('ship-speed').textContent = shipSpeed.toFixed(1);
1045
-
1046
- const headings = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'];
1047
- const headingIndex = Math.round((shipRotation + Math.PI) / (Math.PI / 4)) % 8;
1048
- document.getElementById('heading').textContent = headings[headingIndex];
1049
-
1050
- // Update wind indicator
1051
- const windArrow = document.getElementById('wind-arrow');
1052
- windArrow.style.transform = `rotate(${gameState.windDirection * 180 / Math.PI}deg)`;
1053
-
1054
- // Update tower button states
1055
- document.querySelectorAll('.tower-button').forEach(button => {
1056
- if (button.dataset.cost) {
1057
- const cost = parseInt(button.dataset.cost);
1058
- button.classList.toggle('disabled', gameState.gold < cost);
1059
- }
1060
- });
1061
- }
1062
-
1063
  function cycleTowerSelection() {
1064
  const towers = ['cannon', 'harpoon', 'net', 'lighthouse'];
1065
  const currentIndex = towers.indexOf(gameState.selectedTower);
@@ -1078,7 +1392,6 @@
1078
  }
1079
 
1080
  function restartGame() {
1081
- // Reset game state
1082
  gameState = {
1083
  gold: 500,
1084
  lives: 3,
@@ -1093,18 +1406,16 @@
1093
  projectiles: [],
1094
  particles: [],
1095
  windDirection: 0,
1096
- windSpeed: 5
 
 
1097
  };
1098
 
1099
- // Clear scene
1100
  [...gameState.enemies, ...gameState.towers, ...gameState.projectiles, ...gameState.particles]
1101
  .forEach(obj => scene.remove(obj));
1102
 
1103
- // Reset ship position
1104
  playerShip.position.set(0, 1, 0);
1105
  shipRotation = 0;
1106
- shipSpeed = 0;
1107
- sailsOut = 0.5;
1108
 
1109
  document.getElementById('game-over').style.display = 'none';
1110
  document.getElementById('start-wave').style.display = 'block';
@@ -1112,15 +1423,6 @@
1112
  updateUI();
1113
  }
1114
 
1115
- // Environmental effects
1116
- function updateEnvironment() {
1117
- // Change wind periodically
1118
- if (Math.random() < 0.001) {
1119
- gameState.windDirection += (Math.random() - 0.5) * 0.5;
1120
- gameState.windSpeed = 3 + Math.random() * 7;
1121
- }
1122
- }
1123
-
1124
  // Lighting
1125
  const ambientLight = new THREE.AmbientLight(0x404070, 0.4);
1126
  scene.add(ambientLight);
@@ -1140,7 +1442,6 @@
1140
 
1141
  if (gameState.gameRunning) {
1142
  updateShipMovement();
1143
- updateShipPhysics();
1144
  updateCamera();
1145
  updateTowers();
1146
  updateProjectiles();
@@ -1151,21 +1452,18 @@
1151
  updateUI();
1152
  }
1153
 
1154
- // Update water animation
1155
  lakeMaterial.uniforms.time.value = time;
1156
  skyMaterial.uniforms.time.value = time;
1157
 
1158
  renderer.render(scene, camera);
1159
  }
1160
 
1161
- // Window resize handler
1162
  window.addEventListener('resize', () => {
1163
  camera.aspect = window.innerWidth / window.innerHeight;
1164
  camera.updateProjectionMatrix();
1165
  renderer.setSize(window.innerWidth, window.innerHeight);
1166
  });
1167
 
1168
- // Initialize
1169
  updateUI();
1170
  animate();
1171
  </script>
 
121
  border: 2px solid #8B4513;
122
  }
123
 
124
+ .environment-info {
125
  position: absolute;
126
  bottom: 140px;
127
  left: 20px;
 
142
  padding: 10px;
143
  border-radius: 50%;
144
  border: 2px solid #87CEEB;
145
+ width: 80px;
146
+ height: 80px;
147
  text-align: center;
148
+ font-size: 16px;
149
+ }
150
+
151
+ .area-indicator {
152
+ position: absolute;
153
+ top: 120px;
154
+ left: 50%;
155
+ transform: translateX(-50%);
156
+ color: #d4af37;
157
+ background: rgba(0, 0, 0, 0.8);
158
+ padding: 10px;
159
+ border-radius: 10px;
160
+ border: 2px solid #8B4513;
161
+ text-align: center;
162
+ min-width: 150px;
163
  }
164
 
165
  .health-bar {
 
206
  font-size: 16px;
207
  margin-top: 20px;
208
  }
209
+
210
+ .cargo-panel {
211
+ position: absolute;
212
+ top: 180px;
213
+ right: 20px;
214
+ color: #d4af37;
215
+ background: rgba(0, 0, 0, 0.8);
216
+ padding: 15px;
217
+ border-radius: 10px;
218
+ border: 2px solid #8B4513;
219
+ min-width: 200px;
220
+ max-height: 300px;
221
+ overflow-y: auto;
222
+ }
223
+
224
+ .cargo-item {
225
+ display: flex;
226
+ justify-content: space-between;
227
+ align-items: center;
228
+ margin: 5px 0;
229
+ padding: 5px;
230
+ background: rgba(139, 69, 19, 0.3);
231
+ border-radius: 5px;
232
+ font-size: 14px;
233
+ }
234
+
235
+ .cargo-emoji {
236
+ font-size: 18px;
237
+ margin-right: 8px;
238
+ }
239
+
240
+ .island-trade-panel {
241
+ position: absolute;
242
+ bottom: 180px;
243
+ right: 20px;
244
+ color: #d4af37;
245
+ background: rgba(0, 0, 0, 0.9);
246
+ padding: 15px;
247
+ border-radius: 10px;
248
+ border: 2px solid #FFD700;
249
+ min-width: 250px;
250
+ display: none;
251
+ }
252
+
253
+ .trade-button {
254
+ background: rgba(0, 128, 0, 0.8);
255
+ border: 2px solid #32CD32;
256
+ color: #d4af37;
257
+ padding: 5px 10px;
258
+ border-radius: 3px;
259
+ cursor: pointer;
260
+ font-family: Georgia, serif;
261
+ font-size: 12px;
262
+ margin: 2px;
263
+ }
264
+
265
+ .trade-button:hover {
266
+ background: rgba(0, 200, 0, 0.8);
267
+ }
268
+
269
+ .trade-button.buy {
270
+ background: rgba(0, 0, 128, 0.8);
271
+ border-color: #4169E1;
272
+ }
273
+
274
+ .trade-button.buy:hover {
275
+ background: rgba(0, 0, 200, 0.8);
276
+ }
277
+
278
+ .island-indicator {
279
+ position: absolute;
280
+ top: 50%;
281
+ left: 50%;
282
+ transform: translate(-50%, -50%);
283
+ color: #FFD700;
284
+ background: rgba(0, 0, 0, 0.8);
285
+ padding: 10px 20px;
286
+ border-radius: 10px;
287
+ border: 2px solid #FFD700;
288
+ font-size: 18px;
289
+ display: none;
290
+ z-index: 200;
291
+ }
292
  </style>
293
  </head>
294
  <body>
295
  <div class="ui-container">
296
  <div class="controls ui-panel">
297
+ <h3>⛵ Ship Controls</h3>
298
+ <p>🎮 WASD: Direct ship movement</p>
 
299
  <p>🎯 Click: Place towers</p>
300
+ <p>⚡ Tab: Cycle towers</p>
301
+ <p>🔄 Shift: Boost speed</p>
302
  </div>
303
 
304
  <div class="resource-panel ui-panel">
 
307
  <p>🏴‍☠️ Lives: <span id="lives">3</span></p>
308
  <p>⚔️ Wave: <span id="wave">1</span></p>
309
  <p>🎯 Score: <span id="score">0</span></p>
310
+ <p>📦 Cargo: <span id="cargo-count">0</span>/<span id="cargo-capacity">6</span></p>
311
+ </div>
312
+
313
+ <div class="cargo-panel ui-panel">
314
+ <h3>📦 Cargo Hold</h3>
315
+ <div id="cargo-list">
316
+ <p style="font-style: italic; color: #888;">Empty hold</p>
317
+ </div>
318
  </div>
319
 
320
  <div class="wind-indicator ui-panel">
321
+ <div id="wind-arrow" style="font-size: 24px;">💨</div>
322
+ <div style="font-size: 12px; margin-top: 5px;">
323
+ <span id="wind-speed">5</span> kts
324
+ </div>
325
+ </div>
326
+
327
+ <div class="area-indicator ui-panel">
328
+ <h4 id="current-area">Upper Lake</h4>
329
+ <p id="area-effect">Calm Waters</p>
330
  </div>
331
 
332
  <div class="tower-shop ui-panel">
 
344
  </button>
345
  </div>
346
 
347
+ <div class="environment-info ui-panel">
348
+ <h4>🌊 Environment</h4>
 
349
  <p>Ship Speed: <span id="ship-speed">0</span> kts</p>
350
+ <p>Tide: <span id="tide-info">Normal</span></p>
351
+ <p>Wind Effect: <span id="wind-effect">+0%</span></p>
352
  </div>
353
 
354
  <div class="wave-info ui-panel">
 
358
  <button id="start-wave" class="tower-button">Start Wave</button>
359
  </div>
360
 
361
+ <div class="island-trade-panel ui-panel" id="trade-panel">
362
+ <h3>🏝️ Island Trading Post</h3>
363
+ <div id="trade-content"></div>
364
+ </div>
365
+
366
+ <div class="island-indicator ui-panel" id="island-indicator">
367
+ ⚓ Press E to Trade ⚓
368
+ </div>
369
+
370
  <div class="health-bar ui-panel">
371
  <div class="health-fill" id="health-fill" style="width: 100%"></div>
372
  </div>
 
395
  projectiles: [],
396
  particles: [],
397
  windDirection: 0,
398
+ windSpeed: 5,
399
+ currentArea: "Upper Lake",
400
+ shipSpeed: 0,
401
+ cargo: [],
402
+ cargoCapacity: 6,
403
+ nearIsland: null
404
+ };
405
+
406
+ // Cargo types with their emoji representations and base values
407
+ const cargoTypes = {
408
+ fish: { emoji: '🐟', name: 'Fresh Fish', baseValue: 30, rarity: 0.4 },
409
+ lumber: { emoji: '🪵', name: 'Lumber', baseValue: 25, rarity: 0.3 },
410
+ grain: { emoji: '🌾', name: 'Grain', baseValue: 20, rarity: 0.5 },
411
+ gems: { emoji: '💎', name: 'Gems', baseValue: 100, rarity: 0.05 },
412
+ gold: { emoji: '🪙', name: 'Gold Coins', baseValue: 75, rarity: 0.1 },
413
+ spices: { emoji: '🌶️', name: 'Spices', baseValue: 50, rarity: 0.15 },
414
+ pottery: { emoji: '🏺', name: 'Pottery', baseValue: 35, rarity: 0.2 },
415
+ tools: { emoji: '🔨', name: 'Tools', baseValue: 40, rarity: 0.25 },
416
+ cloth: { emoji: '🧶', name: 'Fine Cloth', baseValue: 45, rarity: 0.18 },
417
+ wine: { emoji: '🍷', name: 'Wine', baseValue: 60, rarity: 0.12 }
418
+ };
419
+
420
+ // Lake areas with different environmental effects
421
+ const lakeAreas = {
422
+ "Upper Lake": {
423
+ center: { x: 0, z: 0 },
424
+ radius: 50,
425
+ windMultiplier: 1.0,
426
+ tideEffect: 0,
427
+ description: "Calm Waters",
428
+ color: 0x87CEEB
429
+ },
430
+ "Wayzata Bay": {
431
+ center: { x: -80, z: -60 },
432
+ radius: 40,
433
+ windMultiplier: 1.5,
434
+ tideEffect: 0.2,
435
+ description: "Strong Winds",
436
+ color: 0x4682B4
437
+ },
438
+ "Crystal Bay": {
439
+ center: { x: 70, z: -50 },
440
+ radius: 35,
441
+ windMultiplier: 0.7,
442
+ tideEffect: -0.1,
443
+ description: "Sheltered Cove",
444
+ color: 0x20B2AA
445
+ },
446
+ "Carmans Bay": {
447
+ center: { x: -50, z: 80 },
448
+ radius: 30,
449
+ windMultiplier: 0.8,
450
+ tideEffect: 0.3,
451
+ description: "Choppy Waters",
452
+ color: 0x008B8B
453
+ },
454
+ "Smithtown Bay": {
455
+ center: { x: 60, z: 70 },
456
+ radius: 32,
457
+ windMultiplier: 1.3,
458
+ tideEffect: -0.2,
459
+ description: "Swift Currents",
460
+ color: 0x5F9EA0
461
+ }
462
  };
463
 
464
  // Scene setup
 
471
  document.body.appendChild(renderer.domElement);
472
 
473
  // Camera controls
474
+ let cameraOffset = new THREE.Vector3(0, 25, 30);
475
 
476
  // Player ship
477
  let playerShip;
478
  let shipRotation = 0;
 
 
479
  const keys = {};
480
+ const baseSpeed = 0.4;
481
 
482
  // Atmospheric fog
483
  scene.fog = new THREE.Fog(0x2d1810, 50, 1000);
 
523
  const lakeGeometry = new THREE.PlaneGeometry(2000, 2000, 200, 200);
524
  const lakeMaterial = new THREE.ShaderMaterial({
525
  uniforms: {
526
+ time: { value: 0 },
527
+ playerPos: { value: new THREE.Vector2(0, 0) }
528
  },
529
  vertexShader: `
530
  uniform float time;
531
+ uniform vec2 playerPos;
532
  varying vec2 vUv;
533
  varying vec3 vPosition;
534
+ varying float vDistanceToPlayer;
535
 
536
  void main() {
537
  vUv = uv;
538
  vec3 pos = position;
539
 
540
+ // Distance-based wave intensity
541
+ float distToPlayer = length(vec2(pos.x, pos.y) - playerPos);
542
+ vDistanceToPlayer = distToPlayer;
543
+
544
+ // Area-specific wave patterns
545
  float wave1 = sin(pos.x * 0.02 + time * 0.5) * 0.2;
546
  float wave2 = sin(pos.y * 0.015 + time * 0.7) * 0.15;
547
  float wave3 = sin((pos.x + pos.y) * 0.01 + time * 0.3) * 0.25;
548
+
549
+ // Stronger waves in certain areas
550
+ float areaEffect = sin(pos.x * 0.005) * sin(pos.y * 0.004) * 0.1;
551
+
552
+ pos.z = wave1 + wave2 + wave3 + areaEffect;
553
 
554
  vPosition = pos;
555
  gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
 
557
  `,
558
  fragmentShader: `
559
  uniform float time;
560
+ uniform vec2 playerPos;
561
  varying vec2 vUv;
562
  varying vec3 vPosition;
563
+ varying float vDistanceToPlayer;
564
 
565
  void main() {
566
  vec3 deepWater = vec3(0.1, 0.3, 0.5);
567
  vec3 shallowWater = vec3(0.3, 0.5, 0.7);
568
+ vec3 currentWater = vec3(0.2, 0.6, 0.8);
569
+
570
+ // Area-based water coloring
571
+ float areaBlend = sin(vPosition.x * 0.003 + time * 0.1) * sin(vPosition.y * 0.002 + time * 0.15);
572
 
573
  float wave = sin(vPosition.x * 0.1 + time) * sin(vPosition.y * 0.08 + time);
574
+ vec3 baseColor = mix(deepWater, shallowWater, wave * 0.5 + 0.5);
575
 
576
+ // Add current effect in certain areas
577
+ vec3 finalColor = mix(baseColor, currentWater, areaBlend * 0.3);
578
+
579
+ gl_FragColor = vec4(finalColor, 0.8);
580
  }
581
  `,
582
  transparent: true
 
585
  const lake = new THREE.Mesh(lakeGeometry, lakeMaterial);
586
  scene.add(lake);
587
 
588
+ // Create visible lake sections with different colors
589
+ Object.entries(lakeAreas).forEach(([name, area]) => {
590
+ const sectionGeometry = new THREE.RingGeometry(area.radius * 0.9, area.radius, 32);
591
  const sectionMaterial = new THREE.MeshBasicMaterial({
592
+ color: area.color,
593
  transparent: true,
594
+ opacity: 0.15,
595
  side: THREE.DoubleSide
596
  });
597
  const section = new THREE.Mesh(sectionGeometry, sectionMaterial);
598
  section.rotation.x = -Math.PI / 2;
599
+ section.position.set(area.center.x, 0.1, area.center.z);
600
+ section.userData = { name: name, area: area };
601
  scene.add(section);
602
+ });
 
 
 
 
 
 
 
603
 
604
  // Create islands and shoreline features
605
+ function createIsland(x, z, size, name = null) {
606
  const islandGeometry = new THREE.CylinderGeometry(size, size * 1.2, 2, 16);
607
  const islandMaterial = new THREE.MeshLambertMaterial({ color: 0x4d5d3d });
608
  const island = new THREE.Mesh(islandGeometry, islandMaterial);
609
  island.position.set(x, 0, z);
610
  island.castShadow = true;
611
+
612
+ // Generate random cargo offerings and demands for this island
613
+ const availableCargo = [];
614
+ const demandedCargo = [];
615
+
616
+ Object.keys(cargoTypes).forEach(type => {
617
+ if (Math.random() < cargoTypes[type].rarity) {
618
+ availableCargo.push({
619
+ type: type,
620
+ quantity: Math.floor(Math.random() * 3) + 1,
621
+ price: Math.floor(cargoTypes[type].baseValue * (0.7 + Math.random() * 0.6))
622
+ });
623
+ }
624
+
625
+ if (Math.random() < 0.3) {
626
+ demandedCargo.push({
627
+ type: type,
628
+ quantity: Math.floor(Math.random() * 2) + 1,
629
+ price: Math.floor(cargoTypes[type].baseValue * (1.2 + Math.random() * 0.8))
630
+ });
631
+ }
632
+ });
633
+
634
+ island.userData = {
635
+ type: 'island',
636
+ name: name || `Island ${Math.floor(Math.random() * 100)}`,
637
+ size: size,
638
+ availableCargo: availableCargo,
639
+ demandedCargo: demandedCargo,
640
+ lastRestock: Date.now()
641
+ };
642
+
643
+ // Add visual cargo indicators on the island
644
+ availableCargo.forEach((cargo, index) => {
645
+ const indicatorGeometry = new THREE.PlaneGeometry(1, 1);
646
+ const indicatorMaterial = new THREE.MeshBasicMaterial({
647
+ map: createCargoTexture(cargoTypes[cargo.type].emoji),
648
+ transparent: true
649
+ });
650
+ const indicator = new THREE.Mesh(indicatorGeometry, indicatorMaterial);
651
+ indicator.position.set(
652
+ x + (Math.cos(index * Math.PI / 2) * size * 0.7),
653
+ 3,
654
+ z + (Math.sin(index * Math.PI / 2) * size * 0.7)
655
+ );
656
+ indicator.rotation.x = -Math.PI / 4;
657
+ scene.add(indicator);
658
+ });
659
+
660
  scene.add(island);
661
 
662
  // Add some trees
 
675
  return island;
676
  }
677
 
678
+ // Create emoji texture for cargo indicators
679
+ function createCargoTexture(emoji) {
680
+ const canvas = document.createElement('canvas');
681
+ canvas.width = 64;
682
+ canvas.height = 64;
683
+ const context = canvas.getContext('2d');
684
+ context.font = '48px Arial';
685
+ context.textAlign = 'center';
686
+ context.textBaseline = 'middle';
687
+ context.fillText(emoji, 32, 32);
688
+
689
+ const texture = new THREE.Texture(canvas);
690
+ texture.needsUpdate = true;
691
+ return texture;
692
+ }
693
+
694
+ // Create strategic islands for tower placement and trading
695
  const islands = [
696
+ createIsland(-20, -30, 8, "Merchant's Harbor"),
697
+ createIsland(30, -20, 6, "Fisher's Cove"),
698
+ createIsland(-40, 20, 7, "Timber Point"),
699
+ createIsland(25, 35, 5, "Spice Island"),
700
+ createIsland(0, -50, 9, "Grand Bazaar"),
701
+ createIsland(-60, -10, 6, "Gold Coast"),
702
+ createIsland(45, 10, 7, "Pottery Bay"),
703
+ createIsland(-70, -50, 5, "Tool Town"),
704
+ createIsland(55, -40, 6, "Wine Harbor"),
705
+ createIsland(-35, 65, 7, "Gem Isle"),
706
+ createIsland(40, 60, 6, "Cloth Port")
707
  ];
708
 
709
  // Create player ship
 
711
  const shipGroup = new THREE.Group();
712
 
713
  // Hull
714
+ const hullGeometry = new THREE.BoxGeometry(2.5, 0.6, 8);
715
  const hullMaterial = new THREE.MeshLambertMaterial({ color: 0x8B4513 });
716
  const hull = new THREE.Mesh(hullGeometry, hullMaterial);
717
+ hull.position.y = 0.3;
718
  shipGroup.add(hull);
719
 
720
+ // Deck
721
+ const deckGeometry = new THREE.BoxGeometry(2, 0.1, 7);
722
+ const deckMaterial = new THREE.MeshLambertMaterial({ color: 0xDEB887 });
723
+ const deck = new THREE.Mesh(deckGeometry, deckMaterial);
724
+ deck.position.y = 0.65;
725
+ shipGroup.add(deck);
726
+
727
  // Mast
728
+ const mastGeometry = new THREE.CylinderGeometry(0.15, 0.15, 10);
729
  const mastMaterial = new THREE.MeshLambertMaterial({ color: 0x654321 });
730
  const mast = new THREE.Mesh(mastGeometry, mastMaterial);
731
+ mast.position.y = 5.6;
732
  shipGroup.add(mast);
733
 
734
+ // Main sail
735
+ const sailGeometry = new THREE.PlaneGeometry(4, 6);
736
  const sailMaterial = new THREE.MeshLambertMaterial({
737
  color: 0xffffff,
738
  transparent: true,
 
740
  side: THREE.DoubleSide
741
  });
742
  const sail = new THREE.Mesh(sailGeometry, sailMaterial);
743
+ sail.position.set(2, 5, 0);
744
  sail.userData = { type: 'sail' };
745
  shipGroup.add(sail);
746
 
747
+ // Bow decoration
748
+ const bowGeometry = new THREE.ConeGeometry(0.3, 1.5, 8);
749
+ const bowMaterial = new THREE.MeshLambertMaterial({ color: 0xFFD700 });
750
+ const bow = new THREE.Mesh(bowGeometry, bowMaterial);
751
+ bow.rotation.x = Math.PI / 2;
752
+ bow.position.set(0, 1, 4);
753
+ shipGroup.add(bow);
754
+
755
  shipGroup.position.set(0, 1, 0);
756
  return shipGroup;
757
  }
 
759
  playerShip = createPlayerShip();
760
  scene.add(playerShip);
761
 
762
+ // Enemy ship creation (same as before)
763
  function createEnemyShip() {
764
  const shipGroup = new THREE.Group();
765
 
 
766
  const hullGeometry = new THREE.BoxGeometry(1.5, 0.4, 4);
767
  const hullMaterial = new THREE.MeshLambertMaterial({ color: 0x2c1810 });
768
  const hull = new THREE.Mesh(hullGeometry, hullMaterial);
769
  hull.position.y = 0.2;
770
  shipGroup.add(hull);
771
 
 
772
  const flagGeometry = new THREE.PlaneGeometry(1, 1);
773
  const flagMaterial = new THREE.MeshLambertMaterial({
774
  color: 0x000000,
 
782
  return shipGroup;
783
  }
784
 
785
+ // Tower creation functions (same as before but keeping them)
786
  function createCannonTower(position) {
787
  const towerGroup = new THREE.Group();
788
 
 
789
  const baseGeometry = new THREE.CylinderGeometry(1.5, 2, 2);
790
  const baseMaterial = new THREE.MeshLambertMaterial({ color: 0x654321 });
791
  const base = new THREE.Mesh(baseGeometry, baseMaterial);
792
  towerGroup.add(base);
793
 
 
794
  const cannonGeometry = new THREE.CylinderGeometry(0.2, 0.3, 2);
795
  const cannonMaterial = new THREE.MeshLambertMaterial({ color: 0x2c2c2c });
796
  const cannon = new THREE.Mesh(cannonGeometry, cannonMaterial);
 
814
  function createHarpoonTower(position) {
815
  const towerGroup = new THREE.Group();
816
 
 
817
  const baseGeometry = new THREE.BoxGeometry(2, 1, 2);
818
  const baseMaterial = new THREE.MeshLambertMaterial({ color: 0x8B7355 });
819
  const base = new THREE.Mesh(baseGeometry, baseMaterial);
820
  towerGroup.add(base);
821
 
 
822
  const launcherGeometry = new THREE.BoxGeometry(0.5, 0.5, 3);
823
  const launcherMaterial = new THREE.MeshLambertMaterial({ color: 0x2c2c2c });
824
  const launcher = new THREE.Mesh(launcherGeometry, launcherMaterial);
 
841
  function createNetTower(position) {
842
  const towerGroup = new THREE.Group();
843
 
 
844
  const baseGeometry = new THREE.CylinderGeometry(1, 1.5, 1.5);
845
  const baseMaterial = new THREE.MeshLambertMaterial({ color: 0x4d5d3d });
846
  const base = new THREE.Mesh(baseGeometry, baseMaterial);
847
  towerGroup.add(base);
848
 
 
849
  const netGeometry = new THREE.TorusGeometry(1, 0.1, 8, 16);
850
  const netMaterial = new THREE.MeshLambertMaterial({ color: 0x8B8B00 });
851
  const net = new THREE.Mesh(netGeometry, netMaterial);
 
869
  function createLighthouseTower(position) {
870
  const towerGroup = new THREE.Group();
871
 
 
872
  const baseGeometry = new THREE.CylinderGeometry(1.5, 2, 8);
873
  const baseMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
874
  const base = new THREE.Mesh(baseGeometry, baseMaterial);
875
  base.position.y = 4;
876
  towerGroup.add(base);
877
 
 
878
  const lightGeometry = new THREE.SphereGeometry(0.5);
879
  const lightMaterial = new THREE.MeshBasicMaterial({ color: 0xFFFF00 });
880
  const light = new THREE.Mesh(lightGeometry, lightMaterial);
881
  light.position.y = 8.5;
882
  towerGroup.add(light);
883
 
 
884
  const areaLight = new THREE.PointLight(0xFFFF00, 1, 60);
885
  areaLight.position.y = 8.5;
886
  towerGroup.add(areaLight);
 
899
  return towerGroup;
900
  }
901
 
902
+ // Improved ship movement with area effects
903
+ function updateShipMovement() {
904
+ const currentArea = getCurrentArea();
905
+ const areaData = lakeAreas[currentArea];
906
+
907
+ // Base movement speed
908
+ let moveSpeed = baseSpeed;
909
+
910
+ // Apply area effects
911
+ if (areaData) {
912
+ // Wind effect
913
+ const windEffect = areaData.windMultiplier;
914
+ moveSpeed *= windEffect;
915
+
916
+ // Tide effect (affects movement in certain directions)
917
+ const tideBoost = areaData.tideEffect;
918
+ moveSpeed += tideBoost * 0.2;
919
+ }
920
+
921
+ // Speed boost with Shift
922
+ if (keys['ShiftLeft'] || keys['ShiftRight']) {
923
+ moveSpeed *= 1.5;
924
+ }
925
+
926
+ // Direct WASD movement
927
+ let movement = new THREE.Vector3(0, 0, 0);
928
+
929
+ if (keys['KeyW']) movement.z -= moveSpeed;
930
+ if (keys['KeyS']) movement.z += moveSpeed;
931
+ if (keys['KeyA']) movement.x -= moveSpeed;
932
+ if (keys['KeyD']) movement.x += moveSpeed;
933
+
934
+ // Apply movement
935
+ if (movement.length() > 0) {
936
+ movement.normalize().multiplyScalar(moveSpeed);
937
+ playerShip.position.add(movement);
938
+
939
+ // Rotate ship to face movement direction
940
+ if (movement.length() > 0) {
941
+ shipRotation = Math.atan2(movement.x, movement.z);
942
+ playerShip.rotation.y = shipRotation;
943
+ }
944
+ }
945
+
946
+ // Constrain to lake bounds
947
+ playerShip.position.x = Math.max(-95, Math.min(95, playerShip.position.x));
948
+ playerShip.position.z = Math.max(-95, Math.min(95, playerShip.position.z));
949
+
950
+ // Calculate actual speed for display
951
+ gameState.shipSpeed = movement.length() * 10;
952
+
953
+ // Update water shader with player position
954
+ lakeMaterial.uniforms.playerPos.value.set(playerShip.position.x, playerShip.position.z);
955
+ }
956
+
957
+ // Area detection
958
+ function getCurrentArea() {
959
+ const playerPos = playerShip.position;
960
+
961
+ for (const [name, area] of Object.entries(lakeAreas)) {
962
+ const distance = Math.sqrt(
963
+ Math.pow(playerPos.x - area.center.x, 2) +
964
+ Math.pow(playerPos.z - area.center.z, 2)
965
+ );
966
+
967
+ if (distance <= area.radius) {
968
+ return name;
969
+ }
970
+ }
971
+
972
+ return "Open Water";
973
+ }
974
+
975
+ // Environment updates
976
+ function updateEnvironment() {
977
+ const currentArea = getCurrentArea();
978
+ const areaData = lakeAreas[currentArea] || { windMultiplier: 1, tideEffect: 0, description: "Open Waters" };
979
+
980
+ // Update current area if changed
981
+ if (gameState.currentArea !== currentArea) {
982
+ gameState.currentArea = currentArea;
983
+
984
+ // Change wind based on area
985
+ gameState.windSpeed = 3 + Math.random() * 4 + (areaData.windMultiplier - 1) * 2;
986
+ gameState.windDirection += (Math.random() - 0.5) * 0.3;
987
+ }
988
+
989
+ // Random wind changes
990
+ if (Math.random() < 0.002) {
991
+ gameState.windDirection += (Math.random() - 0.5) * 0.5;
992
+ gameState.windSpeed = Math.max(1, Math.min(10, gameState.windSpeed + (Math.random() - 0.5) * 2));
993
+ }
994
+
995
+ // Update sail animation based on area wind
996
+ const sail = playerShip.children.find(child => child.userData.type === 'sail');
997
+ if (sail) {
998
+ const windIntensity = areaData.windMultiplier;
999
+ sail.rotation.y = Math.sin(Date.now() * 0.003) * 0.15 * windIntensity;
1000
+ sail.position.x = 2 + Math.sin(Date.now() * 0.002) * 0.2 * windIntensity;
1001
+ }
1002
+ }
1003
+
1004
+ // Input handling
1005
+ document.addEventListener('keydown', (event) => {
1006
+ keys[event.code] = true;
1007
+
1008
+ if (event.code === 'Tab') {
1009
+ event.preventDefault();
1010
+ cycleTowerSelection();
1011
+ }
1012
+ });
1013
+
1014
+ document.addEventListener('keyup', (event) => {
1015
+ keys[event.code] = false;
1016
+ });
1017
+
1018
+ // Camera following
1019
+ function updateCamera() {
1020
+ const targetPosition = playerShip.position.clone().add(cameraOffset);
1021
+ camera.position.lerp(targetPosition, 0.08);
1022
+ camera.lookAt(playerShip.position);
1023
+ }
1024
+
1025
+ // UI Updates
1026
+ function updateUI() {
1027
+ document.getElementById('gold').textContent = gameState.gold;
1028
+ document.getElementById('lives').textContent = gameState.lives;
1029
+ document.getElementById('wave').textContent = gameState.wave;
1030
+ document.getElementById('score').textContent = gameState.score;
1031
+ document.getElementById('enemies-left').textContent = gameState.enemies.length;
1032
+ document.getElementById('health-fill').style.width = `${gameState.health}%`;
1033
+ document.getElementById('wind-speed').textContent = Math.round(gameState.windSpeed);
1034
+ document.getElementById('ship-speed').textContent = gameState.shipSpeed.toFixed(1);
1035
+
1036
+ // Update area info
1037
+ const currentArea = getCurrentArea();
1038
+ const areaData = lakeAreas[currentArea] || { description: "Open Waters", windMultiplier: 1, tideEffect: 0 };
1039
+
1040
+ document.getElementById('current-area').textContent = currentArea;
1041
+ document.getElementById('area-effect').textContent = areaData.description;
1042
+
1043
+ // Environmental effects
1044
+ const windEffect = ((areaData.windMultiplier - 1) * 100).toFixed(0);
1045
+ document.getElementById('wind-effect').textContent = `${windEffect >= 0 ? '+' : ''}${windEffect}%`;
1046
+
1047
+ const tideText = areaData.tideEffect > 0.1 ? "Outgoing" :
1048
+ areaData.tideEffect < -0.1 ? "Incoming" : "Normal";
1049
+ document.getElementById('tide-info').textContent = tideText;
1050
+
1051
+ // Update wind indicator
1052
+ const windArrow = document.getElementById('wind-arrow');
1053
+ windArrow.style.transform = `rotate(${gameState.windDirection * 180 / Math.PI}deg)`;
1054
+
1055
+ // Update tower button states
1056
+ document.querySelectorAll('.tower-button').forEach(button => {
1057
+ if (button.dataset.cost) {
1058
+ const cost = parseInt(button.dataset.cost);
1059
+ button.classList.toggle('disabled', gameState.gold < cost);
1060
+ }
1061
+ });
1062
+ }
1063
+
1064
+ // All combat and game logic functions remain the same...
1065
  function createProjectile(start, target, type) {
1066
  let projectileGeometry, projectileMaterial;
1067
 
 
1105
  return projectile;
1106
  }
1107
 
 
1108
  function spawnEnemyWave() {
1109
  const waveSize = gameState.wave * 3 + 2;
1110
  const spawnPoints = [
 
1139
  }
1140
  }
1141
 
 
1142
  function updateTowers() {
1143
  const currentTime = Date.now();
1144
 
1145
  gameState.towers.forEach(tower => {
1146
  if (currentTime - tower.userData.lastFire < tower.userData.fireRate) return;
1147
 
 
1148
  let nearestEnemy = null;
1149
  let nearestDistance = Infinity;
1150
 
 
1159
  if (nearestEnemy) {
1160
  tower.userData.lastFire = currentTime;
1161
 
 
1162
  const projectileType = {
1163
  'cannon': 'cannonball',
1164
  'harpoon': 'harpoon',
 
1173
  gameState.projectiles.push(projectile);
1174
  scene.add(projectile);
1175
 
 
1176
  tower.lookAt(nearestEnemy.position);
1177
  }
1178
  });
 
1188
  return;
1189
  }
1190
 
 
1191
  const movement = projectile.userData.direction.clone().multiplyScalar(projectile.userData.speed);
1192
  projectile.position.add(movement);
1193
 
 
1194
  gameState.enemies.forEach((enemy, enemyIndex) => {
1195
  if (projectile.position.distanceTo(enemy.position) < 2) {
 
1196
  const tower = gameState.towers.find(t =>
1197
  t.userData.type === projectile.userData.type.replace('ball', '').replace('lighthouse', 'lighthouse')
1198
  );
 
1200
  if (tower) {
1201
  enemy.userData.health -= tower.userData.damage;
1202
 
 
1203
  if (tower.userData.type === 'net') {
1204
  enemy.userData.slowFactor = tower.userData.slow;
1205
  enemy.userData.slowTime = 3000;
1206
  }
1207
 
 
1208
  createHitEffect(enemy.position);
1209
  }
1210
 
 
1217
 
1218
  function updateEnemies() {
1219
  gameState.enemies.forEach((enemy, index) => {
 
1220
  if (enemy.userData.health <= 0) {
1221
  gameState.gold += enemy.userData.value;
1222
  gameState.score += enemy.userData.value;
 
1225
  return;
1226
  }
1227
 
 
1228
  if (enemy.userData.slowTime > 0) {
1229
  enemy.userData.slowTime -= 16;
1230
  if (enemy.userData.slowTime <= 0) {
 
1232
  }
1233
  }
1234
 
 
1235
  const direction = new THREE.Vector3().subVectors(playerShip.position, enemy.position).normalize();
1236
  const speed = enemy.userData.speed * enemy.userData.slowFactor;
1237
  enemy.position.add(direction.multiplyScalar(speed));
1238
  enemy.lookAt(playerShip.position);
1239
 
 
1240
  if (enemy.position.distanceTo(playerShip.position) < 3) {
1241
  gameState.health -= 20;
1242
  gameState.lives--;
 
1250
  });
1251
  }
1252
 
 
1253
  function createHitEffect(position) {
1254
  for (let i = 0; i < 10; i++) {
1255
  const particleGeometry = new THREE.SphereGeometry(0.1);
 
1284
  }
1285
 
1286
  particle.position.add(particle.userData.velocity);
1287
+ particle.userData.velocity.y -= 0.005;
1288
  particle.material.opacity = particle.userData.life / 60;
1289
  });
1290
  }
1291
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1292
  // Tower placement
1293
  let raycaster = new THREE.Raycaster();
1294
  let mouse = new THREE.Vector2();
 
1358
  }
1359
  });
1360
 
 
1361
  function startWave() {
1362
  gameState.waveActive = true;
1363
  spawnEnemyWave();
 
1374
  }
1375
  }
1376
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1377
  function cycleTowerSelection() {
1378
  const towers = ['cannon', 'harpoon', 'net', 'lighthouse'];
1379
  const currentIndex = towers.indexOf(gameState.selectedTower);
 
1392
  }
1393
 
1394
  function restartGame() {
 
1395
  gameState = {
1396
  gold: 500,
1397
  lives: 3,
 
1406
  projectiles: [],
1407
  particles: [],
1408
  windDirection: 0,
1409
+ windSpeed: 5,
1410
+ currentArea: "Upper Lake",
1411
+ shipSpeed: 0
1412
  };
1413
 
 
1414
  [...gameState.enemies, ...gameState.towers, ...gameState.projectiles, ...gameState.particles]
1415
  .forEach(obj => scene.remove(obj));
1416
 
 
1417
  playerShip.position.set(0, 1, 0);
1418
  shipRotation = 0;
 
 
1419
 
1420
  document.getElementById('game-over').style.display = 'none';
1421
  document.getElementById('start-wave').style.display = 'block';
 
1423
  updateUI();
1424
  }
1425
 
 
 
 
 
 
 
 
 
 
1426
  // Lighting
1427
  const ambientLight = new THREE.AmbientLight(0x404070, 0.4);
1428
  scene.add(ambientLight);
 
1442
 
1443
  if (gameState.gameRunning) {
1444
  updateShipMovement();
 
1445
  updateCamera();
1446
  updateTowers();
1447
  updateProjectiles();
 
1452
  updateUI();
1453
  }
1454
 
 
1455
  lakeMaterial.uniforms.time.value = time;
1456
  skyMaterial.uniforms.time.value = time;
1457
 
1458
  renderer.render(scene, camera);
1459
  }
1460
 
 
1461
  window.addEventListener('resize', () => {
1462
  camera.aspect = window.innerWidth / window.innerHeight;
1463
  camera.updateProjectionMatrix();
1464
  renderer.setSize(window.innerWidth, window.innerHeight);
1465
  });
1466
 
 
1467
  updateUI();
1468
  animate();
1469
  </script>