Advertisement
Guest User

Untitled

a guest
Sep 17th, 2024
1,863
1
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 58.38 KB | Gaming | 1 0
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Split-Screen Multiplayer FPS Game with Detailed Environment</title>
  6. <style>
  7. body { margin: 0; overflow: hidden; }
  8. canvas { display: block; }
  9.  
  10. /* Stamina Bar Styles */
  11. .stamina-container {
  12. position: absolute;
  13. top: 20px;
  14. left: 20px;
  15. width: 200px;
  16. height: 20px;
  17. background-color: #555;
  18. border: 2px solid #000;
  19. border-radius: 5px;
  20. }
  21.  
  22. .stamina-bar {
  23. width: 100%;
  24. height: 100%;
  25. background-color: #00ff00;
  26. border-radius: 5px;
  27. transition: width 0.1s linear;
  28. }
  29.  
  30. /* Ammo Counter Styles */
  31. .ammo-container {
  32. position: absolute;
  33. top: 50px;
  34. left: 20px;
  35. width: 200px;
  36. height: 20px;
  37. background-color: #555;
  38. border: 2px solid #000;
  39. border-radius: 5px;
  40. display: flex;
  41. align-items: center;
  42. padding-left: 5px;
  43. box-sizing: border-box;
  44. }
  45.  
  46. .ammo-bar {
  47. width: 100%;
  48. height: 100%;
  49. background-color: #ffa500; /* Orange */
  50. border-radius: 5px;
  51. transition: width 0.1s linear;
  52. }
  53.  
  54. .ammo-count {
  55. position: absolute;
  56. top: 50px;
  57. left: 230px;
  58. color: #fff;
  59. font-family: Arial, sans-serif;
  60. font-size: 18px;
  61. background-color: rgba(0,0,0,0.5);
  62. padding: 2px 5px;
  63. border-radius: 3px;
  64. }
  65.  
  66. /* Health Hearts Styles */
  67. .health-container {
  68. position: absolute;
  69. top: 80px;
  70. left: 20px;
  71. display: flex;
  72. align-items: center;
  73. }
  74.  
  75. .heart {
  76. width: 30px;
  77. height: 30px;
  78. margin-right: 5px;
  79. background-color: red;
  80. clip-path: polygon(
  81. 50% 0%,
  82. 61% 35%,
  83. 98% 35%,
  84. 68% 57%,
  85. 79% 91%,
  86. 50% 70%,
  87. 21% 91%,
  88. 32% 57%,
  89. 2% 35%,
  90. 39% 35%
  91. );
  92. }
  93.  
  94. /* Crosshair Styles */
  95. .crosshair {
  96. position: absolute;
  97. top: 50%;
  98. left: 50%;
  99. width: 20px;
  100. height: 20px;
  101. margin-left: -10px;
  102. margin-top: -10px;
  103. border: 2px solid #ffffff;
  104. border-radius: 50%;
  105. pointer-events: none;
  106. }
  107.  
  108. /* Ammo Box Pickup Indicator */
  109. .ammo-box-indicator {
  110. position: absolute;
  111. top: 10px;
  112. left: 220px;
  113. width: 50px;
  114. height: 50px;
  115. background-color: #ff8c00; /* Dark Orange */
  116. border: 2px solid #000;
  117. border-radius: 5px;
  118. display: flex;
  119. align-items: center;
  120. justify-content: center;
  121. font-weight: bold;
  122. color: #fff;
  123. opacity: 0.9;
  124. transition: opacity 0.5s ease;
  125. }
  126.  
  127. /* Kill Counter Styles */
  128. .kill-counter {
  129. position: absolute;
  130. top: 110px;
  131. left: 20px;
  132. color: #fff;
  133. font-family: Arial, sans-serif;
  134. font-size: 18px;
  135. background-color: rgba(0,0,0,0.5);
  136. padding: 5px 10px;
  137. border-radius: 5px;
  138. }
  139.  
  140. /* Player 1 UI */
  141. #player1-ui {
  142. position: absolute;
  143. top: 0;
  144. left: 0;
  145. width: 100%;
  146. height: 50%;
  147. overflow: hidden;
  148. }
  149.  
  150. /* Player 2 UI */
  151. #player2-ui {
  152. position: absolute;
  153. top: 50%;
  154. left: 0;
  155. width: 100%;
  156. height: 50%;
  157. overflow: hidden;
  158. }
  159.  
  160. /* Adjust crosshair positions */
  161. #player1-ui .crosshair {
  162. top: 25%;
  163. }
  164.  
  165. #player2-ui .crosshair {
  166. top: 75%;
  167. }
  168.  
  169. /* Canvas adjustments */
  170. #game-canvas {
  171. width: 100%;
  172. height: 100%;
  173. }
  174. </style>
  175. </head>
  176. <body>
  177. <!-- Player 1 UI -->
  178. <div id="player1-ui">
  179. <!-- Stamina Bar -->
  180. <div class="stamina-container" id="stamina-container-p1">
  181. <div class="stamina-bar" id="stamina-bar-p1"></div>
  182. </div>
  183.  
  184. <!-- Ammo Counter -->
  185. <div class="ammo-container" id="ammo-container-p1">
  186. <div class="ammo-bar" id="ammo-bar-p1"></div>
  187. </div>
  188. <div class="ammo-count" id="ammo-count-p1">30/100</div>
  189.  
  190. <!-- Health Hearts -->
  191. <div class="health-container" id="health-container-p1">
  192. <div class="heart" id="heart-p1-1"></div>
  193. <div class="heart" id="heart-p1-2"></div>
  194. <div class="heart" id="heart-p1-3"></div>
  195. </div>
  196.  
  197. <!-- Kill Counter -->
  198. <div class="kill-counter" id="kill-counter-p1">Kills: 0</div>
  199.  
  200. <!-- Crosshair -->
  201. <div class="crosshair"></div>
  202.  
  203. <!-- Ammo Box Pickup Indicator -->
  204. <div class="ammo-box-indicator" id="ammo-box-indicator-p1" style="display: none;">
  205. +30
  206. </div>
  207. </div>
  208.  
  209. <!-- Player 2 UI -->
  210. <div id="player2-ui">
  211. <!-- Stamina Bar -->
  212. <div class="stamina-container" id="stamina-container-p2">
  213. <div class="stamina-bar" id="stamina-bar-p2"></div>
  214. </div>
  215.  
  216. <!-- Ammo Counter -->
  217. <div class="ammo-container" id="ammo-container-p2">
  218. <div class="ammo-bar" id="ammo-bar-p2"></div>
  219. </div>
  220. <div class="ammo-count" id="ammo-count-p2">30/100</div>
  221.  
  222. <!-- Health Hearts -->
  223. <div class="health-container" id="health-container-p2">
  224. <div class="heart" id="heart-p2-1"></div>
  225. <div class="heart" id="heart-p2-2"></div>
  226. <div class="heart" id="heart-p2-3"></div>
  227. </div>
  228.  
  229. <!-- Kill Counter -->
  230. <div class="kill-counter" id="kill-counter-p2">Kills: 0</div>
  231.  
  232. <!-- Crosshair -->
  233. <div class="crosshair"></div>
  234.  
  235. <!-- Ammo Box Pickup Indicator -->
  236. <div class="ammo-box-indicator" id="ammo-box-indicator-p2" style="display: none;">
  237. +30
  238. </div>
  239. </div>
  240.  
  241. <!-- Game Canvas -->
  242. <canvas id="game-canvas"></canvas>
  243.  
  244. <!-- Three.js Library -->
  245. <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
  246. <!-- Simplex Noise for terrain generation -->
  247. <script src="https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.min.js"></script>
  248. <!-- PointerLockControls for better FPS controls -->
  249. <script src="https://cdn.jsdelivr.net/npm/three@0.128/examples/js/controls/PointerLockControls.js"></script>
  250. <script>
  251. let scene, renderer;
  252. let gamepadIndices = [null, null];
  253.  
  254. // Player objects
  255. let players = [
  256. {
  257. id: 0,
  258. controls: null,
  259. pitchObject: null,
  260. camera: null,
  261. velocity: new THREE.Vector3(),
  262. speed: 0.2,
  263. sprintSpeed: 0.4,
  264. rotationSpeed: 0.005,
  265. pitch: 0,
  266. isSprinting: false,
  267. stamina: 100,
  268. maxStamina: 100,
  269. staminaDepletionRate: 15,
  270. staminaRegenRate: 20,
  271. canJump: true,
  272. verticalVelocity: 0,
  273. gravity: -0.5,
  274. jumpStrength: 18,
  275. collider: new THREE.Sphere(new THREE.Vector3(), 1),
  276. ammo: 30,
  277. maxAmmo: 100,
  278. gun: null,
  279. health: 3,
  280. maxHealth: 3,
  281. kills: 0,
  282. model: null,
  283. isDead: false,
  284. respawnTime: 3, // seconds
  285. respawnTimer: 0,
  286. },
  287. {
  288. id: 1,
  289. controls: null,
  290. pitchObject: null,
  291. camera: null,
  292. velocity: new THREE.Vector3(),
  293. speed: 0.2,
  294. sprintSpeed: 0.4,
  295. rotationSpeed: 0.005,
  296. pitch: 0,
  297. isSprinting: false,
  298. stamina: 100,
  299. maxStamina: 100,
  300. staminaDepletionRate: 15,
  301. staminaRegenRate: 20,
  302. canJump: true,
  303. verticalVelocity: 0,
  304. gravity: -0.5,
  305. jumpStrength: 18,
  306. collider: new THREE.Sphere(new THREE.Vector3(), 1),
  307. ammo: 30,
  308. maxAmmo: 100,
  309. gun: null,
  310. health: 3,
  311. maxHealth: 3,
  312. kills: 0,
  313. model: null,
  314. isDead: false,
  315. respawnTime: 3, // seconds
  316. respawnTimer: 0,
  317. }
  318. ];
  319.  
  320. let collidableObjects = []; // Objects to check for collisions
  321.  
  322. // Bullets
  323. let bullets = [];
  324.  
  325. // Ammo Box
  326. let ammoBoxes = [];
  327.  
  328. // UI Elements
  329. const staminaBars = [
  330. document.getElementById('stamina-bar-p1'),
  331. document.getElementById('stamina-bar-p2')
  332. ];
  333. const ammoBars = [
  334. document.getElementById('ammo-bar-p1'),
  335. document.getElementById('ammo-bar-p2')
  336. ];
  337. const ammoCounts = [
  338. document.getElementById('ammo-count-p1'),
  339. document.getElementById('ammo-count-p2')
  340. ];
  341. const healthContainers = [
  342. document.getElementById('health-container-p1'),
  343. document.getElementById('health-container-p2')
  344. ];
  345. const ammoBoxIndicators = [
  346. document.getElementById('ammo-box-indicator-p1'),
  347. document.getElementById('ammo-box-indicator-p2')
  348. ];
  349. const killCounters = [
  350. document.getElementById('kill-counter-p1'),
  351. document.getElementById('kill-counter-p2')
  352. ];
  353.  
  354. // To track previous button states for edge detection
  355. let prevGamepadButtons = [[], []];
  356.  
  357. // Simplex Noise instance
  358. const simplex = new SimplexNoise();
  359.  
  360. // Terrain parameters
  361. const terrainSize = 1000;
  362. const terrainSegments = 256;
  363. const terrainMaxHeight = 20;
  364. const noiseScale = 400;
  365.  
  366. let terrainMesh;
  367. let terrainHeights = []; // To store height data for terrain
  368.  
  369. function init() {
  370. // Create scene
  371. scene = new THREE.Scene();
  372. scene.background = new THREE.Color(0x87ceeb); // Sky blue
  373.  
  374. // Add fog for depth perception
  375. scene.fog = new THREE.Fog(0x87ceeb, 0, 1500);
  376.  
  377. // Create renderer
  378. renderer = new THREE.WebGLRenderer({ antialias: true, canvas: document.getElementById('game-canvas') });
  379. renderer.setSize(window.innerWidth, window.innerHeight);
  380. renderer.shadowMap.enabled = true; // Enable shadow mapping
  381.  
  382. // Create cameras and controls for each player
  383. players.forEach(player => {
  384. // Create camera
  385. player.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);
  386.  
  387. // Create controls hierarchy
  388. player.controls = new THREE.Object3D();
  389. scene.add(player.controls);
  390.  
  391. player.pitchObject = new THREE.Object3D();
  392. player.controls.add(player.pitchObject);
  393.  
  394. player.pitchObject.add(player.camera);
  395.  
  396. // Set initial position
  397. player.controls.position.copy(getRandomSpawnPosition());
  398. player.collider.center.copy(player.controls.position);
  399.  
  400. // Add gun model
  401. addGun(player);
  402.  
  403. // Add player model
  404. addHumanoidPlayerModel(player);
  405. });
  406.  
  407. // Create terrain
  408. createTerrain();
  409.  
  410. // Add lighting
  411. const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
  412. scene.add(ambientLight);
  413.  
  414. const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
  415. directionalLight.position.set(200, 300, 200);
  416. directionalLight.castShadow = true; // Cast shadows
  417. directionalLight.shadow.mapSize.width = 1024;
  418. directionalLight.shadow.mapSize.height = 1024;
  419. directionalLight.shadow.camera.near = 0.5;
  420. directionalLight.shadow.camera.far = 1500;
  421. scene.add(directionalLight);
  422.  
  423. // Add environment elements
  424. addTrees();
  425. addRocks();
  426. addMountains();
  427. addSun();
  428. addClouds();
  429. addGrass();
  430.  
  431. // Handle window resize
  432. window.addEventListener('resize', onWindowResize, false);
  433.  
  434. // Start the game loop
  435. animate();
  436.  
  437. // Handle gamepad connections
  438. window.addEventListener('gamepadconnected', (event) => {
  439. console.log('Gamepad connected:', event.gamepad);
  440. // Assign gamepad to player
  441. assignGamepadToPlayer(event.gamepad.index);
  442. });
  443.  
  444. window.addEventListener('gamepaddisconnected', (event) => {
  445. console.log('Gamepad disconnected:', event.gamepad);
  446. // Remove gamepad assignment
  447. gamepadIndices.forEach((index, i) => {
  448. if (index === event.gamepad.index) {
  449. gamepadIndices[i] = null;
  450. }
  451. });
  452. });
  453.  
  454. // Spawn ammo boxes periodically, max 3 at a time
  455. setInterval(spawnAmmoBox, 10000); // Every 10 seconds
  456.  
  457. // Initialize UI
  458. updateAllUI();
  459. }
  460.  
  461. function assignGamepadToPlayer(gamepadIndex) {
  462. for (let i = 0; i < gamepadIndices.length; i++) {
  463. if (gamepadIndices[i] === null) {
  464. gamepadIndices[i] = gamepadIndex;
  465. prevGamepadButtons[i] = [];
  466. console.log(`Gamepad ${gamepadIndex} assigned to Player ${i + 1}`);
  467. break;
  468. }
  469. }
  470. }
  471.  
  472. function onWindowResize() {
  473. players.forEach(player => {
  474. player.camera.aspect = window.innerWidth / window.innerHeight;
  475. player.camera.updateProjectionMatrix();
  476. });
  477. renderer.setSize(window.innerWidth, window.innerHeight);
  478. }
  479.  
  480. function handleGamepadInput(playerIndex) {
  481. const gamepads = navigator.getGamepads();
  482. const gamepadIndex = gamepadIndices[playerIndex];
  483. if (gamepadIndex === null) return;
  484.  
  485. const gamepad = gamepads[gamepadIndex];
  486. if (!gamepad) return;
  487.  
  488. const player = players[playerIndex];
  489.  
  490. // Initialize previous buttons if not set
  491. if (!prevGamepadButtons[playerIndex]) {
  492. prevGamepadButtons[playerIndex] = gamepad.buttons.map(button => button.pressed);
  493. }
  494.  
  495. // Skip input if player is dead
  496. if (player.isDead) return;
  497.  
  498. // Detect sprinting via left stick click (button 10)
  499. const sprintButtonIndex = 10;
  500. const isSprintPressed = gamepad.buttons[sprintButtonIndex]?.pressed;
  501. player.isSprinting = isSprintPressed && player.stamina > 0;
  502.  
  503. // Determine current speed
  504. let currentSpeed = player.isSprinting ? player.sprintSpeed : player.speed;
  505.  
  506. // Handle stamina
  507. if (player.isSprinting && player.stamina > 0) {
  508. player.stamina -= player.staminaDepletionRate * (1 / 60); // Assuming 60 FPS
  509. if (player.stamina < 0) player.stamina = 0;
  510. } else if (!player.isSprinting && player.stamina < player.maxStamina) {
  511. player.stamina += player.staminaRegenRate * (1 / 60);
  512. if (player.stamina > player.maxStamina) player.stamina = player.maxStamina;
  513. }
  514.  
  515. // Update stamina bar
  516. updateStaminaBar(player);
  517.  
  518. // If stamina is depleted, stop sprinting
  519. if (player.stamina <= 0) {
  520. player.isSprinting = false;
  521. }
  522.  
  523. // Left stick axes for movement (axes 0 and 1)
  524. const moveX = gamepad.axes[0]; // Left/right strafing
  525. const moveZ = gamepad.axes[1]; // Forward/backward
  526.  
  527. // Movement inputs
  528. const moveForward = -moveZ; // Invert to align with camera
  529. const moveRight = moveX;
  530.  
  531. // Compute movement direction based on controls' orientation
  532. const forward = new THREE.Vector3(0, 0, -1);
  533. forward.applyQuaternion(player.controls.quaternion);
  534. forward.y = 0;
  535. forward.normalize();
  536.  
  537. const right = new THREE.Vector3(1, 0, 0);
  538. right.applyQuaternion(player.controls.quaternion);
  539. right.y = 0;
  540. right.normalize();
  541.  
  542. // Update velocity
  543. player.velocity.x = (forward.x * moveForward + right.x * moveRight) * currentSpeed;
  544. player.velocity.z = (forward.z * moveForward + right.z * moveRight) * currentSpeed;
  545.  
  546. // Attempt to move controls
  547. const oldPosition = player.controls.position.clone();
  548. player.controls.position.x += player.velocity.x;
  549. player.controls.position.z += player.velocity.z;
  550.  
  551. // Update player's collider position
  552. player.collider.center.set(player.controls.position.x, player.controls.position.y, player.controls.position.z);
  553.  
  554. // Check for collisions
  555. if (checkCollisions(player)) {
  556. // Collision detected, revert to old position
  557. player.controls.position.copy(oldPosition);
  558. player.collider.center.set(player.controls.position.x, player.controls.position.y, player.controls.position.z);
  559. }
  560.  
  561. // Apply rotations with smoothing
  562. const rotationDeltaX = gamepad.axes[2] * player.rotationSpeed;
  563. const rotationDeltaY = gamepad.axes[3] * player.rotationSpeed;
  564.  
  565. player.controls.rotation.y -= rotationDeltaX;
  566.  
  567. player.pitch -= rotationDeltaY;
  568. player.pitch = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, player.pitch));
  569.  
  570. // Smoothly interpolate the pitch rotation
  571. player.pitchObject.rotation.x += (player.pitch - player.pitchObject.rotation.x) * 0.1; // Smoothing factor
  572.  
  573. // Handle Jumping
  574. const jumpButtonIndex = 0; // "A" button
  575. const isJumpPressed = gamepad.buttons[jumpButtonIndex]?.pressed;
  576. const wasJumpPressed = prevGamepadButtons[playerIndex][jumpButtonIndex];
  577.  
  578. // Detect jump on button press (not hold)
  579. if (isJumpPressed && !wasJumpPressed && player.canJump && isOnGround(player)) {
  580. player.verticalVelocity = player.jumpStrength;
  581. player.canJump = false;
  582. }
  583.  
  584. // Apply gravity
  585. player.verticalVelocity += player.gravity;
  586. player.controls.position.y += player.verticalVelocity * (1 / 60); // Assuming 60 FPS
  587.  
  588. // Adjust terrain height at player's position
  589. const terrainHeight = getTerrainHeight(player.controls.position.x, player.controls.position.z);
  590. if (player.controls.position.y < terrainHeight + 5) { // Ground level (player height is 5)
  591. player.controls.position.y = terrainHeight + 5;
  592. player.verticalVelocity = 0;
  593. player.canJump = true;
  594. }
  595.  
  596. // Update player's collider position
  597. player.collider.center.y = player.controls.position.y;
  598.  
  599. // Update player model position
  600. if (player.model) {
  601. player.model.position.copy(player.controls.position);
  602. }
  603.  
  604. // Handle Shooting
  605. const fireButtonIndex = 5; // Right trigger
  606. const isFirePressed = gamepad.buttons[fireButtonIndex]?.pressed;
  607. const wasFirePressed = prevGamepadButtons[playerIndex][fireButtonIndex];
  608.  
  609. // Detect fire on button press (not hold)
  610. if (isFirePressed && !wasFirePressed && player.ammo > 0) {
  611. fireBullet(player);
  612. }
  613.  
  614. // Update previous button states
  615. prevGamepadButtons[playerIndex] = gamepad.buttons.map(button => button.pressed);
  616. }
  617.  
  618. function isOnGround(player) {
  619. const terrainHeight = getTerrainHeight(player.controls.position.x, player.controls.position.z);
  620. return player.controls.position.y <= terrainHeight + 5 + 0.01; // Small epsilon for floating point precision
  621. }
  622.  
  623. function checkCollisions(player) {
  624. for (let i = 0; i < collidableObjects.length; i++) {
  625. const obj = collidableObjects[i];
  626. if (obj.userData.collider) {
  627. const collision = player.collider.intersectsSphere(obj.userData.collider);
  628. if (collision) {
  629. return true; // Collision detected
  630. }
  631. }
  632. }
  633. return false; // No collision
  634. }
  635.  
  636. function animate() {
  637. requestAnimationFrame(animate);
  638.  
  639. // Handle gamepad input for each player
  640. players.forEach((player, index) => {
  641. handleGamepadInput(index);
  642. updatePlayerModel(player);
  643. handleRespawn(player);
  644. });
  645.  
  646. // Update bullets
  647. updateBullets();
  648.  
  649. // Animate ammo boxes
  650. animateAmmoBoxes();
  651.  
  652. // Check for ammo box pickups
  653. players.forEach((player, index) => {
  654. pickUpAmmoBox(player);
  655. });
  656.  
  657. // Render the scene for each player
  658. renderSplitScreen();
  659.  
  660. // Update UI
  661. updateAllUI();
  662. }
  663.  
  664. function renderSplitScreen() {
  665. const width = window.innerWidth;
  666. const height = window.innerHeight;
  667.  
  668. // Player 1
  669. // Hide player 1's own model and show player 2's model
  670. players[0].model.visible = false;
  671. players[1].model.visible = true;
  672.  
  673. renderer.setViewport(0, height / 2, width, height / 2);
  674. renderer.setScissor(0, height / 2, width, height / 2);
  675. renderer.setScissorTest(true);
  676. renderer.render(scene, players[0].camera);
  677.  
  678. // Player 2
  679. // Hide player 2's own model and show player 1's model
  680. players[1].model.visible = false;
  681. players[0].model.visible = true;
  682.  
  683. renderer.setViewport(0, 0, width, height / 2);
  684. renderer.setScissor(0, 0, width, height / 2);
  685. renderer.setScissorTest(true);
  686. renderer.render(scene, players[1].camera);
  687.  
  688. // Reset models' visibility
  689. players[0].model.visible = true;
  690. players[1].model.visible = true;
  691.  
  692. renderer.setScissorTest(false);
  693. }
  694.  
  695. function createTerrain() {
  696. const geometry = new THREE.PlaneGeometry(terrainSize, terrainSize, terrainSegments, terrainSegments);
  697. geometry.rotateX(-Math.PI / 2); // Rotate to make it horizontal
  698.  
  699. // Apply noise to vertices to create hills and valleys
  700. for (let i = 0; i < geometry.attributes.position.count; i++) {
  701. const x = geometry.attributes.position.getX(i);
  702. const z = geometry.attributes.position.getZ(i);
  703. const y = simplex.noise2D(x / noiseScale, z / noiseScale) * terrainMaxHeight;
  704. geometry.attributes.position.setY(i, y);
  705. terrainHeights.push(y);
  706. }
  707.  
  708. geometry.computeVertexNormals();
  709.  
  710. const material = new THREE.MeshLambertMaterial({ color: 0x013220 }); // Dark evergreen
  711.  
  712. terrainMesh = new THREE.Mesh(geometry, material);
  713. terrainMesh.receiveShadow = true;
  714. scene.add(terrainMesh);
  715. }
  716.  
  717. function getTerrainHeight(x, z) {
  718. // Convert x, z to terrain grid indices
  719. const halfSize = terrainSize / 2;
  720. const gridX = Math.floor(((x + halfSize) / terrainSize) * terrainSegments);
  721. const gridZ = Math.floor(((z + halfSize) / terrainSize) * terrainSegments);
  722.  
  723. // Clamp indices to terrain grid
  724. const clampedX = THREE.MathUtils.clamp(gridX, 0, terrainSegments - 1);
  725. const clampedZ = THREE.MathUtils.clamp(gridZ, 0, terrainSegments - 1);
  726.  
  727. // Get the height from terrainHeights array
  728. const index = clampedZ * (terrainSegments + 1) + clampedX;
  729. return terrainHeights[index] || 0;
  730. }
  731.  
  732. // Environment functions
  733. function addTrees() {
  734. // Define tree types with varying canopy sizes
  735. const treeTypes = [
  736. {
  737. trunkHeight: 15,
  738. trunkRadius: 1.5,
  739. canopyParts: 6,
  740. canopyRadius: 5,
  741. trunkColor: 0x5d4037, // Darker brown
  742. canopyColors: [0x2e7d32, 0x388e3c, 0x1b5e20], // Varied dark greens
  743. spreadCanopy: false
  744. },
  745. {
  746. trunkHeight: 18,
  747. trunkRadius: 1.8,
  748. canopyParts: 7,
  749. canopyRadius: 6,
  750. trunkColor: 0x6d4c41, // Slightly different brown
  751. canopyColors: [0x43a047, 0x2e7d32, 0x1b5e20],
  752. spreadCanopy: false
  753. },
  754. {
  755. trunkHeight: 12,
  756. trunkRadius: 1.2,
  757. canopyParts: 5,
  758. canopyRadius: 4,
  759. trunkColor: 0x5d4037,
  760. canopyColors: [0x2e7d32, 0x388e3c],
  761. spreadCanopy: false
  762. },
  763. // Additional larger tree types
  764. {
  765. trunkHeight: 22,
  766. trunkRadius: 2.2,
  767. canopyParts: 8,
  768. canopyRadius: 7,
  769. trunkColor: 0x4e342e, // Even darker brown
  770. canopyColors: [0x1b5e20, 0x2e7d32, 0x006400], // Varied dark greens
  771. spreadCanopy: false
  772. },
  773. {
  774. trunkHeight: 25,
  775. trunkRadius: 2.5,
  776. canopyParts: 9,
  777. canopyRadius: 8,
  778. trunkColor: 0x3e2723, // Very dark brown
  779. canopyColors: [0x1b5e20, 0x2e7d32, 0x006400],
  780. spreadCanopy: false
  781. }
  782. ];
  783.  
  784. // Number of trees to add
  785. const numberOfTrees = 300;
  786.  
  787. for (let i = 0; i < numberOfTrees; i++) {
  788. // Choose a random tree type
  789. const type = treeTypes[Math.floor(Math.random() * treeTypes.length)];
  790.  
  791. // Randomly decide if this tree will have a spread-out canopy
  792. const spreadCanopyChance = 0.3; // 30% chance
  793. const hasSpreadCanopy = Math.random() < spreadCanopyChance;
  794. type.spreadCanopy = hasSpreadCanopy;
  795.  
  796. // Create a group for each tree
  797. const tree = new THREE.Group();
  798.  
  799. // Create trunk
  800. const trunkGeometry = new THREE.CylinderGeometry(type.trunkRadius, type.trunkRadius, type.trunkHeight, 8);
  801. const trunkMaterial = new THREE.MeshLambertMaterial({ color: type.trunkColor });
  802. const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial);
  803. trunk.position.y = type.trunkHeight / 2;
  804. trunk.castShadow = true;
  805. trunk.receiveShadow = true;
  806. tree.add(trunk);
  807.  
  808. // Create canopy parts
  809. if (type.spreadCanopy) {
  810. // Spread-out canopy: arrange spheres around the top
  811. const numCanopySpheres = type.canopyParts;
  812. const angleStep = (Math.PI * 2) / numCanopySpheres;
  813. const radius = type.canopyRadius * 1.2;
  814.  
  815. for (let j = 0; j < numCanopySpheres; j++) {
  816. const canopyGeometry = new THREE.SphereGeometry(type.canopyRadius, 8, 8);
  817. const canopyMaterial = new THREE.MeshLambertMaterial({ color: type.canopyColors[Math.floor(Math.random() * type.canopyColors.length)] });
  818. const canopy = new THREE.Mesh(canopyGeometry, canopyMaterial);
  819. const angle = j * angleStep;
  820. canopy.position.x = Math.cos(angle) * radius;
  821. canopy.position.z = Math.sin(angle) * radius;
  822. canopy.position.y = type.trunkHeight + type.canopyRadius; // Slight overlap
  823. canopy.castShadow = true;
  824. canopy.receiveShadow = true;
  825. tree.add(canopy);
  826. }
  827. } else {
  828. // Tall canopy: stack spheres vertically
  829. for (let j = 0; j < type.canopyParts; j++) {
  830. const canopyGeometry = new THREE.SphereGeometry(type.canopyRadius, 8, 8);
  831. const canopyMaterial = new THREE.MeshLambertMaterial({ color: type.canopyColors[Math.floor(Math.random() * type.canopyColors.length)] });
  832. const canopy = new THREE.Mesh(canopyGeometry, canopyMaterial);
  833. canopy.position.y = type.trunkHeight + (type.canopyRadius * 0.8) * (j + 1);
  834. canopy.castShadow = true;
  835. canopy.receiveShadow = true;
  836. tree.add(canopy);
  837. }
  838. }
  839.  
  840. // Position the tree on the terrain
  841. const x = Math.random() * terrainSize - terrainSize / 2;
  842. const z = Math.random() * terrainSize - terrainSize / 2;
  843. const y = getTerrainHeight(x, z); // Get terrain height at (x, z)
  844. tree.position.set(x, y, z);
  845.  
  846. // Add collider for trunk only
  847. const trunkCollider = new THREE.Sphere(new THREE.Vector3(x, y + type.trunkHeight / 2, z), type.trunkRadius * 1.2);
  848. collidableObjects.push({ userData: { collider: trunkCollider } });
  849.  
  850. // Add to scene
  851. scene.add(tree);
  852. }
  853. }
  854.  
  855. function addRocks() {
  856. // Add small rocks
  857. const smallRockGeometry = new THREE.DodecahedronGeometry(0.7, 0);
  858. const smallRockMaterial = new THREE.MeshLambertMaterial({ color: 0x808080 }); // Gray color
  859.  
  860. for (let i = 0; i < 300; i++) {
  861. const rock = new THREE.Mesh(smallRockGeometry, smallRockMaterial);
  862. const x = Math.random() * terrainSize - terrainSize / 2;
  863. const z = Math.random() * terrainSize - terrainSize / 2;
  864. const y = getTerrainHeight(x, z) + 0.35; // Slightly above ground
  865. rock.position.set(x, y, z);
  866. rock.rotation.set(Math.random() * Math.PI, Math.random() * Math.PI, Math.random() * Math.PI);
  867. rock.castShadow = true;
  868. rock.receiveShadow = true;
  869. scene.add(rock);
  870. // Small rocks do not have collision detection
  871. }
  872.  
  873. // Add large rocks
  874. const largeRockGeometry = new THREE.DodecahedronGeometry(3.0, 0); // Increased size
  875. const largeRockMaterial = new THREE.MeshLambertMaterial({ color: 0x696969 }); // Darker gray
  876.  
  877. for (let i = 0; i < 40; i++) {
  878. const rock = new THREE.Mesh(largeRockGeometry, largeRockMaterial);
  879. const x = Math.random() * terrainSize - terrainSize / 2;
  880. const z = Math.random() * terrainSize - terrainSize / 2;
  881. const y = getTerrainHeight(x, z) + 3.0; // Slightly above ground
  882. rock.position.set(x, y, z);
  883. rock.rotation.set(Math.random() * Math.PI, Math.random() * Math.PI, Math.random() * Math.PI);
  884. rock.castShadow = true;
  885. rock.receiveShadow = true;
  886. scene.add(rock);
  887.  
  888. // Add collider to large rocks
  889. const collider = new THREE.Sphere(new THREE.Vector3(x, y, z), 4); // Approximate radius
  890. rock.userData.collider = collider;
  891.  
  892. // Add to collidable objects
  893. collidableObjects.push(rock);
  894. }
  895.  
  896. // Add very large rocks
  897. const veryLargeRockGeometry = new THREE.DodecahedronGeometry(6.0, 0); // 6 times bigger
  898. const veryLargeRockMaterial = new THREE.MeshLambertMaterial({ color: 0x555555 }); // Even darker gray
  899.  
  900. for (let i = 0; i < 10; i++) {
  901. const rock = new THREE.Mesh(veryLargeRockGeometry, veryLargeRockMaterial);
  902. const x = Math.random() * terrainSize - terrainSize / 2;
  903. const z = Math.random() * terrainSize - terrainSize / 2;
  904. const y = getTerrainHeight(x, z) + 6.0; // Slightly above ground
  905. rock.position.set(x, y, z);
  906. rock.rotation.set(Math.random() * Math.PI, Math.random() * Math.PI, Math.random() * Math.PI);
  907. rock.castShadow = true;
  908. rock.receiveShadow = true;
  909. scene.add(rock);
  910.  
  911. // Add collider to very large rocks
  912. const collider = new THREE.Sphere(new THREE.Vector3(x, y, z), 8); // Approximate radius
  913. rock.userData.collider = collider;
  914.  
  915. // Add to collidable objects
  916. collidableObjects.push(rock);
  917. }
  918. }
  919.  
  920. function addMountains() {
  921. const mountainGroup = new THREE.Group();
  922. const numberOfMountains = 50; // Increased from 30 to 50
  923. const radius = 1200; // Increased distance from the center/player
  924.  
  925. for (let i = 0; i < numberOfMountains; i++) {
  926. // Random angle for placement
  927. const angle = (i / numberOfMountains) * Math.PI * 2;
  928.  
  929. // Randomize mountain size with more variation
  930. const mountainSizeType = Math.floor(Math.random() * 3); // 0: small, 1: medium, 2: large
  931. let mountainHeight, mountainWidth;
  932.  
  933. if (mountainSizeType === 0) { // Small
  934. mountainHeight = THREE.MathUtils.randFloat(40, 60);
  935. mountainWidth = THREE.MathUtils.randFloat(100, 200);
  936. } else if (mountainSizeType === 1) { // Medium
  937. mountainHeight = THREE.MathUtils.randFloat(60, 100);
  938. mountainWidth = THREE.MathUtils.randFloat(200, 300);
  939. } else { // Large
  940. mountainHeight = THREE.MathUtils.randFloat(100, 150);
  941. mountainWidth = THREE.MathUtils.randFloat(300, 500);
  942. }
  943.  
  944. // Create mountain geometry (simple cone)
  945. const mountainGeometry = new THREE.ConeGeometry(mountainWidth, mountainHeight, 16);
  946. const mountainMaterial = new THREE.MeshLambertMaterial({ color: 0x808080 }); // Grayish color
  947. const mountain = new THREE.Mesh(mountainGeometry, mountainMaterial);
  948.  
  949. // Position the mountain
  950. mountain.position.x = Math.cos(angle) * radius;
  951. mountain.position.z = Math.sin(angle) * radius;
  952.  
  953. // Adjust Y position so that part of the mountain is below the terrain
  954. const terrainHeightAtMountain = getTerrainHeight(mountain.position.x, mountain.position.z);
  955. const overlap = THREE.MathUtils.randFloat(10, 20); // Random overlap between 10 to 20 units
  956. mountain.position.y = (mountainHeight / 2) + terrainHeightAtMountain - overlap;
  957.  
  958. // Rotate the mountain to face outward
  959. mountain.rotation.y = angle + Math.PI / 2;
  960.  
  961. // Optionally, add slight randomness to position for more natural look
  962. mountain.position.x += THREE.MathUtils.randFloatSpread(50);
  963. mountain.position.z += THREE.MathUtils.randFloatSpread(50);
  964.  
  965. // Disable casting shadows for mountains to improve performance
  966. mountain.castShadow = false;
  967. mountain.receiveShadow = false;
  968.  
  969. // Add to mountain group
  970. mountainGroup.add(mountain);
  971. }
  972.  
  973. // Add the mountain group to the scene
  974. scene.add(mountainGroup);
  975. }
  976.  
  977. function addSun() {
  978. // Create sun as a 2D circle using CircleGeometry and MeshBasicMaterial
  979. const sunGeometry = new THREE.CircleGeometry(30, 32); // Radius 30, 32 segments for smoothness
  980. const sunMaterial = new THREE.MeshBasicMaterial({ color: 0xffe066, side: THREE.DoubleSide }); // Muted yellow color
  981. const sun = new THREE.Mesh(sunGeometry, sunMaterial);
  982.  
  983. // Position the sun in the sky
  984. sun.position.set(-600, 800, -600); // Farther position for a larger sky
  985. sun.rotation.x = -Math.PI / 2; // Rotate to face the camera
  986.  
  987. // Add sun to the scene
  988. scene.add(sun);
  989. }
  990.  
  991. function addClouds() {
  992. const cloudGroup = new THREE.Group();
  993. const numberOfClouds = 40; // Increased from 20 to 40
  994.  
  995. for (let i = 0; i < numberOfClouds; i++) {
  996. // Create cloud geometry using multiple spheres
  997. const cloudGeometry = new THREE.SphereGeometry(15, 8, 8);
  998. const cloudMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff, transparent: true, opacity: 0.8 });
  999. const cloud = new THREE.Mesh(cloudGeometry, cloudMaterial);
  1000.  
  1001. // Create a group for each cloud
  1002. const singleCloud = new THREE.Group();
  1003.  
  1004. // Add multiple spheres to form a cloud
  1005. const numSpheres = THREE.MathUtils.randInt(4, 8);
  1006. for (let j = 0; j < numSpheres; j++) {
  1007. const sphere = new THREE.Mesh(cloudGeometry, cloudMaterial);
  1008. sphere.position.set(
  1009. THREE.MathUtils.randFloatSpread(30),
  1010. THREE.MathUtils.randFloatSpread(10),
  1011. THREE.MathUtils.randFloatSpread(30)
  1012. );
  1013. singleCloud.add(sphere);
  1014. }
  1015.  
  1016. // Position the cloud
  1017. const x = Math.random() * terrainSize - terrainSize / 2;
  1018. const z = Math.random() * terrainSize - terrainSize / 2;
  1019. const y = THREE.MathUtils.randFloat(200, 400);
  1020. singleCloud.position.set(x, y, z);
  1021.  
  1022. // Random scale
  1023. const scale = THREE.MathUtils.randFloat(0.5, 2);
  1024. singleCloud.scale.set(scale, scale, scale);
  1025.  
  1026. // Add to cloud group
  1027. cloudGroup.add(singleCloud);
  1028. }
  1029.  
  1030. // Add cloud group to the scene
  1031. scene.add(cloudGroup);
  1032. }
  1033.  
  1034. function addGrass() {
  1035. const grassGroup = new THREE.Group();
  1036. const grassBladeGeometry = new THREE.BoxGeometry(0.1, 2, 0.05); // Increased height
  1037. const grassBladeMaterial = new THREE.MeshLambertMaterial({ color: 0x006400 }); // Darker green
  1038.  
  1039. const numberOfBlades = 2000; // Increased from 1000 to 2000
  1040. const groupCount = 100; // Number of grass clusters
  1041. const bladesPerGroup = Math.floor(numberOfBlades / groupCount);
  1042.  
  1043. for (let i = 0; i < groupCount; i++) {
  1044. // Random center position for the grass group
  1045. const centerX = Math.random() * terrainSize - terrainSize / 2;
  1046. const centerZ = Math.random() * terrainSize - terrainSize / 2;
  1047. const centerY = getTerrainHeight(centerX, centerZ);
  1048.  
  1049. // Random number of blades in this group
  1050. const bladesInThisGroup = THREE.MathUtils.randInt(bladesPerGroup - 20, bladesPerGroup + 20);
  1051.  
  1052. for (let j = 0; j < bladesInThisGroup; j++) {
  1053. const blade = new THREE.Mesh(grassBladeGeometry, grassBladeMaterial);
  1054.  
  1055. // Slight offset from the group center
  1056. const x = centerX + THREE.MathUtils.randFloatSpread(10);
  1057. const z = centerZ + THREE.MathUtils.randFloatSpread(10);
  1058. const y = getTerrainHeight(x, z) + 1; // Half the height to sit on the ground
  1059.  
  1060. blade.position.set(x, y, z);
  1061.  
  1062. // Random rotation around the Y-axis
  1063. blade.rotation.y = Math.random() * Math.PI * 2;
  1064.  
  1065. // Random scale for height variation
  1066. const scaleY = THREE.MathUtils.randFloat(0.5, 1.5);
  1067. blade.scale.set(1, scaleY, 1);
  1068.  
  1069. // Add blade to grass group
  1070. grassGroup.add(blade);
  1071. }
  1072. }
  1073.  
  1074. // Add some scattered blades outside the groups
  1075. const scatteredBlades = 400; // Increased from 200 to 400
  1076. for (let i = 0; i < scatteredBlades; i++) {
  1077. const blade = new THREE.Mesh(grassBladeGeometry, grassBladeMaterial);
  1078.  
  1079. // Random position on the ground plane
  1080. const x = Math.random() * terrainSize - terrainSize / 2;
  1081. const z = Math.random() * terrainSize - terrainSize / 2;
  1082. const y = getTerrainHeight(x, z) + 1; // Half the height to sit on the ground
  1083.  
  1084. blade.position.set(x, y, z);
  1085.  
  1086. // Random rotation around the Y-axis
  1087. blade.rotation.y = Math.random() * Math.PI * 2;
  1088.  
  1089. // Random scale for height variation
  1090. const scaleY = THREE.MathUtils.randFloat(0.5, 1.5);
  1091. blade.scale.set(1, scaleY, 1);
  1092.  
  1093. // Add blade to grass group
  1094. grassGroup.add(blade);
  1095. }
  1096.  
  1097. // Add grass group to the scene
  1098. scene.add(grassGroup);
  1099. }
  1100.  
  1101. function addGun(player) {
  1102. // Create a gun using only the barrel geometry
  1103. const gunGroup = new THREE.Group();
  1104.  
  1105. // Barrel Geometry
  1106. const barrelGeometry = new THREE.BoxGeometry(0.3, 0.3, 1.2);
  1107. const barrelMaterial = new THREE.MeshStandardMaterial({ color: 0x444444, metalness: 0.4, roughness: 0.6 });
  1108. const barrel = new THREE.Mesh(barrelGeometry, barrelMaterial);
  1109. barrel.position.set(0, 0, 0); // Center the barrel
  1110. barrel.castShadow = true;
  1111. barrel.receiveShadow = true;
  1112. gunGroup.add(barrel);
  1113.  
  1114. // Align the gun correctly in front of the camera
  1115. gunGroup.position.set(0.6, -0.5, -2); // Adjust position as needed
  1116. gunGroup.rotation.x = Math.PI / 12; // Slight angle for realism
  1117.  
  1118. // Add gun to pitchObject so it follows the camera's rotation
  1119. player.pitchObject.add(gunGroup);
  1120.  
  1121. // Attach gun to player object for reference
  1122. player.gun = gunGroup;
  1123. }
  1124.  
  1125. function fireBullet(player) {
  1126. if (player.ammo <= 0) return;
  1127.  
  1128. // Create bullet geometry
  1129. const bulletGeometry = new THREE.SphereGeometry(0.3, 16, 16);
  1130. const bulletMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
  1131. const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
  1132.  
  1133. // Position the bullet at the gun's end
  1134. const gunPosition = new THREE.Vector3();
  1135. player.gun.getWorldPosition(gunPosition);
  1136.  
  1137. // Calculate the direction the camera is facing
  1138. const direction = new THREE.Vector3();
  1139. player.camera.getWorldDirection(direction);
  1140. direction.normalize();
  1141.  
  1142. // Offset the bullet slightly forward from the gun to prevent clipping
  1143. gunPosition.add(direction.clone().multiplyScalar(0.5));
  1144. bullet.position.copy(gunPosition);
  1145.  
  1146. // Set bullet velocity
  1147. const bulletSpeed = 100;
  1148. bullet.velocity = direction.clone().multiplyScalar(bulletSpeed);
  1149.  
  1150. // Bullet owner
  1151. bullet.ownerId = player.id;
  1152.  
  1153. // Add gravity to bullet after a certain distance or time
  1154. bullet.hasGravity = false;
  1155. bullet.fallDistance = 100; // Distance after which gravity starts
  1156. bullet.distanceTraveled = 0;
  1157.  
  1158. // Add bullet to the scene and bullets array
  1159. scene.add(bullet);
  1160. bullets.push(bullet);
  1161.  
  1162. // Reduce ammo
  1163. player.ammo -= 1;
  1164. updateAmmoBar(player);
  1165. }
  1166.  
  1167. function updateBullets() {
  1168. for (let i = bullets.length - 1; i >= 0; i--) {
  1169. const bullet = bullets[i];
  1170. // Move bullet forward
  1171. const deltaTime = 1 / 60; // Assuming 60 FPS
  1172. const moveDistance = bullet.velocity.clone().multiplyScalar(deltaTime);
  1173. bullet.position.add(moveDistance);
  1174.  
  1175. // Update distance traveled
  1176. bullet.distanceTraveled += moveDistance.length();
  1177.  
  1178. // Apply gravity after falling distance
  1179. if (!bullet.hasGravity && bullet.distanceTraveled >= bullet.fallDistance) {
  1180. bullet.hasGravity = true;
  1181. }
  1182.  
  1183. if (bullet.hasGravity) {
  1184. bullet.velocity.y += players[bullet.ownerId].gravity * deltaTime; // Apply gravity
  1185. }
  1186.  
  1187. // Update bullet position with gravity
  1188. bullet.position.y += bullet.velocity.y * deltaTime;
  1189.  
  1190. // Remove bullet if it's too far or below ground
  1191. if (bullet.position.length() > 1500 || bullet.position.y < getTerrainHeight(bullet.position.x, bullet.position.z)) {
  1192. scene.remove(bullet);
  1193. bullets.splice(i, 1);
  1194. continue;
  1195. }
  1196.  
  1197. // Collision detection with players
  1198. players.forEach(player => {
  1199. if (player.id !== bullet.ownerId && !player.isDead) {
  1200. const distance = player.controls.position.distanceTo(bullet.position);
  1201. if (distance < 2) { // Adjust as needed
  1202. // Bullet hit the player
  1203. player.health -= 1;
  1204. updateHealthUI(player);
  1205. if (player.health <= 0) {
  1206. handlePlayerDeath(player, players[bullet.ownerId]);
  1207. }
  1208. // Remove bullet
  1209. scene.remove(bullet);
  1210. bullets.splice(i, 1);
  1211. }
  1212. }
  1213. });
  1214. }
  1215. }
  1216.  
  1217. function handlePlayerDeath(deadPlayer, killerPlayer) {
  1218. deadPlayer.isDead = true;
  1219. deadPlayer.respawnTimer = deadPlayer.respawnTime;
  1220.  
  1221. // Hide player model
  1222. if (deadPlayer.model) {
  1223. deadPlayer.model.visible = false;
  1224. }
  1225.  
  1226. // Increment killer's kill count
  1227. killerPlayer.kills += 1;
  1228. updateKillCounter(killerPlayer);
  1229. }
  1230.  
  1231. function handleRespawn(player) {
  1232. if (player.isDead) {
  1233. player.respawnTimer -= 1 / 60; // Assuming 60 FPS
  1234. if (player.respawnTimer <= 0) {
  1235. player.isDead = false;
  1236. player.health = player.maxHealth;
  1237. player.ammo = player.maxAmmo;
  1238. player.stamina = player.maxStamina;
  1239. player.controls.position.copy(getRandomSpawnPosition());
  1240. player.collider.center.copy(player.controls.position);
  1241. if (player.model) {
  1242. player.model.visible = true;
  1243. player.model.position.copy(player.controls.position);
  1244. }
  1245. updateAllUI();
  1246. }
  1247. }
  1248. }
  1249.  
  1250. function getRandomSpawnPosition() {
  1251. let x = Math.random() * terrainSize - terrainSize / 2;
  1252. let z = Math.random() * terrainSize - terrainSize / 2;
  1253. let y = getTerrainHeight(x, z) + 5;
  1254. return new THREE.Vector3(x, y, z);
  1255. }
  1256.  
  1257. function addHumanoidPlayerModel(player) {
  1258. const color = player.id === 0 ? 0xff0000 : 0x0000ff; // Red or Blue
  1259. const material = new THREE.MeshLambertMaterial({ color });
  1260.  
  1261. const bodyGroup = new THREE.Group();
  1262.  
  1263. // Adjusted positions
  1264. const modelHeight = 6; // Total height of the model
  1265.  
  1266. // Torso
  1267. const torsoGeometry = new THREE.BoxGeometry(2, 3, 1);
  1268. const torso = new THREE.Mesh(torsoGeometry, material);
  1269. torso.position.y = 3.5;
  1270. bodyGroup.add(torso);
  1271.  
  1272. // Head
  1273. const headGeometry = new THREE.BoxGeometry(1.5, 1.5, 1.5);
  1274. const head = new THREE.Mesh(headGeometry, material);
  1275. head.position.y = 5.75;
  1276. bodyGroup.add(head);
  1277.  
  1278. // Arms
  1279. const armGeometry = new THREE.BoxGeometry(0.5, 3, 0.5);
  1280. // Left Arm
  1281. const leftArm = new THREE.Mesh(armGeometry, material);
  1282. leftArm.position.set(-1.25, 3.5, 0);
  1283. bodyGroup.add(leftArm);
  1284. // Right Arm
  1285. const rightArm = new THREE.Mesh(armGeometry, material);
  1286. rightArm.position.set(1.25, 3.5, 0);
  1287. bodyGroup.add(rightArm);
  1288.  
  1289. // Legs
  1290. const legGeometry = new THREE.BoxGeometry(0.7, 3, 0.7);
  1291. // Left Leg
  1292. const leftLeg = new THREE.Mesh(legGeometry, material);
  1293. leftLeg.position.set(-0.5, 1, 0);
  1294. bodyGroup.add(leftLeg);
  1295. // Right Leg
  1296. const rightLeg = new THREE.Mesh(legGeometry, material);
  1297. rightLeg.position.set(0.5, 1, 0);
  1298. bodyGroup.add(rightLeg);
  1299.  
  1300. // Position the model at the player's position
  1301. bodyGroup.position.copy(player.controls.position);
  1302.  
  1303. // Adjust model's origin to be at the feet
  1304. bodyGroup.position.y -= modelHeight / 2;
  1305.  
  1306. scene.add(bodyGroup);
  1307. player.model = bodyGroup;
  1308. }
  1309.  
  1310. function updatePlayerModel(player) {
  1311. if (player.model) {
  1312. player.model.position.copy(player.controls.position);
  1313. // Adjust model's origin to be at the feet
  1314. player.model.position.y -= 6 / 2; // modelHeight / 2
  1315. // Optional: Make the model face the camera's direction
  1316. player.model.rotation.y = player.controls.rotation.y;
  1317. }
  1318. }
  1319.  
  1320. function pickUpAmmoBox(player) {
  1321. if (player.isDead) return;
  1322.  
  1323. for (let i = ammoBoxes.length - 1; i >= 0; i--) {
  1324. const box = ammoBoxes[i];
  1325. const distance = player.controls.position.distanceTo(box.position);
  1326. if (distance < 5) { // Increased pickup distance threshold
  1327. // Add 30 bullets, but do not exceed maxAmmo
  1328. player.ammo += 30;
  1329. if (player.ammo > player.maxAmmo) player.ammo = player.maxAmmo;
  1330. updateAmmoBar(player);
  1331.  
  1332. // Remove ammo box from scene and array
  1333. scene.remove(box);
  1334. ammoBoxes.splice(i, 1);
  1335.  
  1336. // Show ammo box pickup indicator
  1337. showAmmoBoxIndicator(player);
  1338.  
  1339. break;
  1340. }
  1341. }
  1342. }
  1343.  
  1344. function showAmmoBoxIndicator(player) {
  1345. const indicator = ammoBoxIndicators[player.id];
  1346. indicator.style.display = 'flex';
  1347. indicator.style.opacity = '1';
  1348. setTimeout(() => {
  1349. indicator.style.opacity = '0';
  1350. setTimeout(() => {
  1351. indicator.style.display = 'none';
  1352. }, 500); // Fade out duration
  1353. }, 2000); // Show for 2 seconds
  1354. }
  1355.  
  1356. function animateAmmoBoxes() {
  1357. ammoBoxes.forEach(box => {
  1358. // Wobble animation using sine wave
  1359. box.userData.wobblePhase += 0.02;
  1360. const baseY = getTerrainHeight(box.position.x, box.position.z) + 1.5;
  1361. box.position.y = baseY + Math.sin(box.userData.wobblePhase) * 0.3; // More pronounced bobbing
  1362.  
  1363. // Slow rotation
  1364. box.rotation.y += 0.005; // Adjust rotation speed as desired
  1365. });
  1366. }
  1367.  
  1368. function addAmmoBox(x, z) {
  1369. // Create ammo box geometry (simple box)
  1370. const boxGeometry = new THREE.BoxGeometry(3, 3, 3);
  1371. const boxMaterial = new THREE.MeshLambertMaterial({ color: 0xff8c00 }); // Dark Orange
  1372. const ammoBox = new THREE.Mesh(boxGeometry, boxMaterial);
  1373.  
  1374. // Position the ammo box on the terrain
  1375. const y = getTerrainHeight(x, z) + 1.5; // Half the height to sit on the ground
  1376. ammoBox.position.set(x, y, z);
  1377.  
  1378. // Enable shadow casting and receiving
  1379. ammoBox.castShadow = true;
  1380. ammoBox.receiveShadow = true;
  1381.  
  1382. // Add floating and wobbling animation parameters
  1383. ammoBox.userData = {
  1384. wobblePhase: Math.random() * Math.PI * 2
  1385. };
  1386.  
  1387. // Add ammo box to the scene and ammoBoxes array
  1388. scene.add(ammoBox);
  1389. ammoBoxes.push(ammoBox);
  1390. }
  1391.  
  1392. function spawnAmmoBox() {
  1393. // Limit the number of ammo boxes to 3
  1394. if (ammoBoxes.length >= 3) return;
  1395.  
  1396. let attempts = 0;
  1397. const maxAttempts = 10;
  1398. let positionFound = false;
  1399. let x, z;
  1400.  
  1401. while (!positionFound && attempts < maxAttempts) {
  1402. x = Math.random() * terrainSize - terrainSize / 2;
  1403. z = Math.random() * terrainSize - terrainSize / 2;
  1404.  
  1405. // Ensure the new ammo box is not too close to existing ones
  1406. const minDistance = 50;
  1407. let tooClose = false;
  1408. for (let i = 0; i < ammoBoxes.length; i++) {
  1409. const existingBox = ammoBoxes[i];
  1410. const distance = Math.sqrt(Math.pow(existingBox.position.x - x, 2) + Math.pow(existingBox.position.z - z, 2));
  1411. if (distance < minDistance) {
  1412. tooClose = true;
  1413. break;
  1414. }
  1415. }
  1416.  
  1417. if (!tooClose) {
  1418. positionFound = true;
  1419. }
  1420.  
  1421. attempts++;
  1422. }
  1423.  
  1424. if (positionFound) {
  1425. addAmmoBox(x, z);
  1426. } else {
  1427. console.warn('Could not find suitable position for ammo box after multiple attempts.');
  1428. }
  1429. }
  1430.  
  1431. function updateAllUI() {
  1432. players.forEach(player => {
  1433. updateStaminaBar(player);
  1434. updateAmmoBar(player);
  1435. updateHealthUI(player);
  1436. updateKillCounter(player);
  1437. });
  1438. }
  1439.  
  1440. function updateStaminaBar(player) {
  1441. const staminaPercentage = (player.stamina / player.maxStamina) * 100;
  1442. staminaBars[player.id].style.width = `${staminaPercentage}%`;
  1443.  
  1444. // Change color based on stamina level
  1445. if (staminaPercentage > 50) {
  1446. staminaBars[player.id].style.backgroundColor = '#00ff00'; // Green
  1447. } else if (staminaPercentage > 20) {
  1448. staminaBars[player.id].style.backgroundColor = '#ffff00'; // Yellow
  1449. } else {
  1450. staminaBars[player.id].style.backgroundColor = '#ff0000'; // Red
  1451. }
  1452. }
  1453.  
  1454. function updateAmmoBar(player) {
  1455. const ammoCountValue = player.ammo;
  1456. const maxAmmo = player.maxAmmo;
  1457. const ammoPercentage = (ammoCountValue / maxAmmo) * 100;
  1458. ammoBars[player.id].style.width = `${ammoPercentage}%`;
  1459. ammoCounts[player.id].textContent = `${ammoCountValue}/${maxAmmo}`; // Updated to show current/max ammo
  1460.  
  1461. // Change color based on ammo level
  1462. if (ammoCountValue > 50) {
  1463. ammoBars[player.id].style.backgroundColor = '#ffa500'; // Orange
  1464. } else if (ammoCountValue > 20) {
  1465. ammoBars[player.id].style.backgroundColor = '#ff4500'; // OrangeRed
  1466. } else {
  1467. ammoBars[player.id].style.backgroundColor = '#ff0000'; // Red
  1468. }
  1469. }
  1470.  
  1471. function updateHealthUI(player) {
  1472. const healthHearts = healthContainers[player.id].querySelectorAll('.heart');
  1473. healthHearts.forEach((heart, index) => {
  1474. if (index < player.health) {
  1475. heart.style.visibility = 'visible';
  1476. } else {
  1477. heart.style.visibility = 'hidden';
  1478. }
  1479. });
  1480. }
  1481.  
  1482. function updateKillCounter(player) {
  1483. killCounters[player.id].textContent = `Kills: ${player.kills}`;
  1484. }
  1485.  
  1486. // Initialize the scene
  1487. init();
  1488. </script>
  1489. </body>
  1490. </html>
  1491.  
Tags: html game
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement