awacke1 commited on
Commit
45786df
·
verified ·
1 Parent(s): 30b7648

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +383 -199
index.html CHANGED
@@ -25,7 +25,7 @@
25
  .player-info { padding: 5px; border-radius: 4px; }
26
  .player1 { background-color: rgba(0, 150, 255, 0.5); }
27
  .player2 { background-color: rgba(255, 100, 0, 0.5); }
28
- .health-bar-container {
29
  position: absolute;
30
  width: 100px;
31
  height: 16px;
@@ -41,23 +41,17 @@
41
  z-index: 20;
42
  display: none; /* Hidden by default, controlled by JS */
43
  }
44
- .health-bar-fill {
45
  height: 100%;
46
- background-color: #4caf50; /* Green */
47
  width: 100%; /* Full by default */
48
  transition: width 0.2s ease-in-out;
49
  }
50
  #gameOverScreen {
51
- position: absolute;
52
- top: 50%; left: 50%;
53
  transform: translate(-50%, -50%);
54
- padding: 30px;
55
- background-color: rgba(20, 20, 20, 0.9);
56
- border: 2px solid #555;
57
- border-radius: 15px;
58
- text-align: center;
59
- display: none;
60
- z-index: 100;
61
  }
62
  #gameOverScreen h2 { margin-top: 0; font-size: 28px; color: #ff4444; }
63
  #gameOverScreen p { font-size: 18px; }
@@ -84,93 +78,138 @@
84
  let scene, camera, renderer, clock;
85
  let players = [];
86
  let playerProjectiles = [];
87
- let babyYars = []; // ***** REPLACED enemyProjectiles
 
88
  let neutralZoneBlocks = [];
89
  let qotile;
 
 
 
 
 
 
90
 
91
  const keysPressed = {};
92
  const gameSettings = {
93
  playerSpeed: 10,
94
  projectileSpeed: 30,
95
- babyYarSpeed: 8,
96
  projectileSize: 0.2,
97
  neutralZoneBlockSize: 2,
98
  qotileSize: 4,
99
- playAreaWidth: 40, // Increased play area
100
  playAreaHeight: 30,
101
  playerShootCooldown: 0.2,
102
- qotileSpawnCooldown: 2.5, // How often Qotile spawns a baby
103
- playerInitialHealth: 150, // ***** NEW
104
- qotileInitialHealth: 250, // Increased health
105
- babyYarInitialHealth: 5, // ***** NEW
106
- babyYarDamage: 15, // Damage a baby yar does on collision
 
 
 
 
 
 
 
 
107
  pointsPerNeutralBlock: 10,
108
  pointsPerBabyYar: 25,
109
- pointsPerQotileHit: 10, // Reduced points for hitting Qotile directly
110
  };
111
  let gameActive = true;
112
 
113
- // ******** NEW: HEALTH BAR MANAGEMENT ********
114
- function createHealthBar(character) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  const container = document.createElement('div');
116
- container.className = 'health-bar-container';
117
-
118
  const fill = document.createElement('div');
119
- fill.className = 'health-bar-fill';
120
-
121
  container.appendChild(fill);
122
  document.body.appendChild(container);
123
 
124
- character.healthBar = {
125
- container: container,
126
- fill: fill,
127
- text: container
128
- };
129
-
130
- // Set specific colors for different characters
131
- if (character.isPlayer) {
132
- fill.style.backgroundColor = '#2196F3'; // Blue for players
133
- } else if (character.isBabyYar) {
134
- fill.style.backgroundColor = '#f44336'; // Red for babies
135
- } else { // Qotile
136
- fill.style.backgroundColor = '#ff9800'; // Orange for boss
137
- container.style.width = '200px';
138
- container.style.height = '20px';
139
- container.style.fontSize = '14px';
140
- container.style.lineHeight = '18px';
 
141
  }
142
  }
143
 
144
- function updateHealthBar(character) {
145
- if (!character.healthBar) return;
146
- const healthPercent = (character.health / character.maxHealth) * 100;
147
- character.healthBar.fill.style.width = `${healthPercent}%`;
148
- character.healthBar.text.textContent = `${Math.ceil(character.health)} / ${character.maxHealth}`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
 
150
- // Position the health bar above the character
151
  const screenPosition = toScreenPosition(character.mesh, camera);
152
- if (screenPosition.z > 1) { // z > 1 means it's clipped, so hide it
153
- character.healthBar.container.style.display = 'none';
154
  } else {
155
- character.healthBar.container.style.display = 'block';
156
- character.healthBar.container.style.left = `${screenPosition.x}px`;
157
- character.healthBar.container.style.top = `${screenPosition.y - 40}px`; // Offset above the model
 
158
  }
159
  }
160
-
161
  function toScreenPosition(obj, camera) {
162
  const vector = new THREE.Vector3();
 
163
  obj.updateMatrixWorld();
164
  vector.setFromMatrixPosition(obj.matrixWorld);
165
  vector.project(camera);
166
-
167
  vector.x = (vector.x * window.innerWidth / 2) + window.innerWidth / 2;
168
  vector.y = -(vector.y * window.innerHeight / 2) + window.innerHeight / 2;
169
-
170
  return vector;
171
  }
172
 
173
-
174
  function init() {
175
  gameActive = true;
176
  scene = new THREE.Scene();
@@ -199,7 +238,7 @@
199
  animate();
200
  }
201
 
202
- function createYarModel(color) { /* ... unchanged ... */
203
  const yarGroup = new THREE.Group();
204
  const bodyGeometry = new THREE.CylinderGeometry(0.2, 0.4, 1.5, 8);
205
  const bodyMaterial = new THREE.MeshStandardMaterial({ color: color, roughness: 0.5 });
@@ -216,7 +255,7 @@
216
  const wingMaterial = new THREE.MeshStandardMaterial({ color: color, transparent: true, opacity: 0.7, side: THREE.DoubleSide });
217
  const wings = [];
218
  for (let i = 0; i < 4; i++) {
219
- const wing = new THREE.Mesh(wingGeometry, wingMaterial);
220
  wing.position.y = 0.2;
221
  yarGroup.add(wing);
222
  wings.push(wing);
@@ -229,9 +268,8 @@
229
  yarGroup.userData.isMoving = false;
230
  return yarGroup;
231
  }
232
-
233
  function createPlayers() {
234
- players.forEach(p => { if (p.healthBar) document.body.removeChild(p.healthBar.container); });
235
  players = [];
236
  playerProjectiles = [];
237
 
@@ -242,9 +280,11 @@
242
  mesh: p1Model, isPlayer: true, isPlayer2: false,
243
  controls: { up: 'KeyW', down: 'KeyS', left: 'KeyA', right: 'KeyD', shoot: 'KeyE' },
244
  shootCooldownTimer: 0, score: 0,
245
- health: gameSettings.playerInitialHealth, maxHealth: gameSettings.playerInitialHealth
 
246
  };
247
- createHealthBar(p1);
 
248
  players.push(p1);
249
 
250
  const p2Model = createYarModel(0xff6600);
@@ -254,13 +294,15 @@
254
  mesh: p2Model, isPlayer: true, isPlayer2: true,
255
  controls: { up: 'KeyI', down: 'KeyK', left: 'KeyJ', right: 'KeyL', shoot: 'KeyU' },
256
  shootCooldownTimer: 0, score: 0,
257
- health: gameSettings.playerInitialHealth, maxHealth: gameSettings.playerInitialHealth
 
258
  };
259
- createHealthBar(p2);
 
260
  players.push(p2);
261
  }
262
 
263
- function createQotileModel() { /* ... unchanged ... */
264
  const qotileGroup = new THREE.Group();
265
  const coreGeometry = new THREE.DodecahedronGeometry(gameSettings.qotileSize, 0);
266
  const coreMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000, emissive: 0x550000, roughness: 0.2 });
@@ -270,14 +312,17 @@
270
  const eyeMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });
271
  const pupilGeometry = new THREE.SphereGeometry(0.3, 12, 12);
272
  const pupilMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 });
273
- const leftEye = new THREE.Mesh(eyeGeometry, eyeMaterial);
274
- leftEye.position.set(-1.2, 1, -3.5);
275
- const rightEye = new THREE.Mesh(eyeGeometry, eyeMaterial);
276
- rightEye.position.set(1.2, 1, -3.5);
 
 
277
  const leftPupil = new THREE.Mesh(pupilGeometry, pupilMaterial);
278
- leftPupil.position.z = -0.6;
279
- const rightPupil = new THREE.Mesh(pupilGeometry, pupilMaterial);
280
- rightPupil.position.z = -0.6;
 
281
  leftEye.add(leftPupil);
282
  rightEye.add(rightPupil);
283
  qotileGroup.add(leftEye);
@@ -286,11 +331,8 @@
286
  return qotileGroup;
287
  }
288
 
289
- function createNeutralZone() { /* ... unchanged ... */
290
- neutralZoneBlocks.forEach(block => {
291
- if(scene.getObjectById(block.id)) scene.remove(block);
292
- if(block.geometry) block.geometry.dispose(); if(block.material) block.material.dispose();
293
- });
294
  neutralZoneBlocks = [];
295
  const blockGeometry = new THREE.BoxGeometry(gameSettings.neutralZoneBlockSize, gameSettings.neutralZoneBlockSize, gameSettings.neutralZoneBlockSize / 2);
296
  const blockMaterial = new THREE.MeshStandardMaterial({ color: 0x888888, roughness: 0.7, metalness: 0.3 });
@@ -306,10 +348,6 @@
306
  }
307
 
308
  function createQotile() {
309
- if (qotile) {
310
- if(qotile.mesh) scene.remove(qotile.mesh);
311
- if(qotile.healthBar) document.body.removeChild(qotile.healthBar.container);
312
- }
313
  const qotileModel = createQotileModel();
314
  qotileModel.position.set(0, 0, -20);
315
  scene.add(qotileModel);
@@ -318,27 +356,28 @@
318
  health: gameSettings.qotileInitialHealth, maxHealth: gameSettings.qotileInitialHealth,
319
  hitTimer: 0, spawnCooldownTimer: gameSettings.qotileSpawnCooldown,
320
  };
321
- createHealthBar(qotile);
322
  }
323
 
324
- // ******** NEW: CREATE BABY YAR MINION ********
325
  function createBabyYar(startPosition) {
326
- const group = createQotileModel(); // Reuse the model but smaller
327
  group.scale.set(0.2, 0.2, 0.2);
328
  group.position.copy(startPosition);
 
329
 
330
  const baby = {
331
  mesh: group, isBabyYar: true,
332
  health: gameSettings.babyYarInitialHealth, maxHealth: gameSettings.babyYarInitialHealth,
333
- velocity: new THREE.Vector3(0, 0, gameSettings.babyYarSpeed)
 
334
  };
335
 
336
- createHealthBar(baby);
337
  scene.add(group);
338
  babyYars.push(baby);
339
  }
340
 
341
- function handlePlayerMovement(player, delta) { /* ... unchanged ... */
342
  let moved = false;
343
  const moveDistance = gameSettings.playerSpeed * delta;
344
  if (keysPressed[player.controls.up]) { player.mesh.position.y += moveDistance; moved = true; }
@@ -352,15 +391,15 @@
352
  player.mesh.position.y = Math.max(-halfHeight, Math.min(halfHeight, player.mesh.position.y));
353
  }
354
 
355
- function handlePlayerShooting(player, delta) { /* ... unchanged ... */
356
  if (player.shootCooldownTimer > 0) player.shootCooldownTimer -= delta;
357
  if (keysPressed[player.controls.shoot] && player.shootCooldownTimer <= 0) {
358
  player.shootCooldownTimer = gameSettings.playerShootCooldown;
359
  createProjectile(player);
360
  }
361
  }
362
-
363
- function createProjectile(player) { /* ... unchanged ... */
364
  const projectileGeometry = new THREE.SphereGeometry(gameSettings.projectileSize, 8, 8);
365
  const projectileMaterial = new THREE.MeshBasicMaterial({ color: player.isPlayer2 ? 0xffaa33 : 0x66ccff });
366
  const projectile = new THREE.Mesh(projectileGeometry, projectileMaterial);
@@ -377,31 +416,89 @@
377
  for (let i = playerProjectiles.length - 1; i >= 0; i--) {
378
  const p = playerProjectiles[i];
379
  p.position.addScaledVector(p.userData.velocity, delta);
380
- if (p.position.z < -30) {
381
- scene.remove(p); p.geometry.dispose(); p.material.dispose();
382
- playerProjectiles.splice(i, 1); continue;
 
383
  }
384
  checkPlayerProjectileCollision(p, i);
385
  }
386
 
387
- // Baby Yars (Minions)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
388
  for (let i = babyYars.length - 1; i >= 0; i--) {
389
  const baby = babyYars[i];
390
- baby.mesh.position.addScaledVector(baby.velocity, delta);
391
-
392
- // Animate baby eyes
393
- const time = clock.getElapsedTime();
394
- baby.mesh.userData.eyes.forEach((eye, j) => {
395
- eye.children[0].position.x = Math.sin(time * 5 + j) * 0.15;
396
- eye.children[0].position.y = Math.cos(time * 5 + j) * 0.15;
 
 
 
397
  });
398
 
399
- if (baby.mesh.position.z > 30) {
400
- scene.remove(baby.mesh);
401
- document.body.removeChild(baby.healthBar.container);
402
- babyYars.splice(i, 1);
403
- continue;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
404
  }
 
405
  checkBabyYarCollision(baby, i);
406
  }
407
  }
@@ -410,89 +507,165 @@
410
  const pSphere = new THREE.Sphere(projectile.position, gameSettings.projectileSize);
411
  const owner = projectile.userData.owner;
412
 
413
- // Vs Neutral Zone
414
- // ... (unchanged logic) ...
415
-
416
- // Vs Baby Yars
 
 
 
 
 
 
 
 
 
 
417
  for (let i = babyYars.length - 1; i >= 0; i--) {
418
  const baby = babyYars[i];
419
  const babyBox = new THREE.Box3().setFromObject(baby.mesh);
420
  if (pSphere.intersectsBox(babyBox)) {
421
- // d4 damage
422
  const damage = Math.floor(Math.random() * 4) + 1;
423
  baby.health -= damage;
424
 
425
  if (baby.health <= 0) {
426
- owner.score += gameSettings.pointsPerBabyYar;
427
- scene.remove(baby.mesh);
428
- document.body.removeChild(baby.healthBar.container);
429
  babyYars.splice(i, 1);
430
  }
431
 
432
- scene.remove(projectile); projectile.geometry.dispose(); projectile.material.dispose();
433
  playerProjectiles.splice(projectileIndex, 1);
434
  updateUI(); return;
435
  }
436
  }
437
 
438
- // Vs Qotile
439
  if (qotile && qotile.health > 0) {
440
  const qotileBox = new THREE.Box3().setFromObject(qotile.mesh);
441
  if (pSphere.intersectsBox(qotileBox)) {
442
  const damage = Math.floor(Math.random() * 4) + 1;
443
  qotile.health -= damage;
444
- owner.score += gameSettings.pointsPerQotileHit;
445
  qotile.mesh.children[0].material.emissive.setHex(0xffffff);
446
  qotile.hitTimer = 0.1;
447
  if (qotile.health <= 0) {
448
- endGame((owner.isPlayer2 ? "Player 2" : "Player 1") + " destroyed the Qotile!");
449
  }
450
- scene.remove(projectile); projectile.geometry.dispose(); projectile.material.dispose();
451
  playerProjectiles.splice(projectileIndex, 1);
452
  updateUI(); return;
453
  }
454
  }
455
  }
456
-
457
- // ******** NEW: COLLISION FOR BABY YARS ********
458
  function checkBabyYarCollision(baby, babyIndex) {
459
- const babySphere = new THREE.Sphere(baby.mesh.position, 1.0); // Hitbox size
460
-
461
  for(let i = players.length - 1; i >= 0; i--) {
462
  const player = players[i];
463
  if (player.health <= 0) continue;
464
  const playerBox = new THREE.Box3().setFromObject(player.mesh);
465
-
466
- if (babySphere.intersectsBox(playerBox)) {
467
  player.health -= gameSettings.babyYarDamage;
468
-
469
- // Cleanup baby
470
- scene.remove(baby.mesh);
471
- document.body.removeChild(baby.healthBar.container);
472
  babyYars.splice(babyIndex, 1);
473
 
474
- // Player hit effect
475
  player.mesh.visible = false;
476
- setTimeout(() => { player.mesh.visible = true; }, 100);
477
- setTimeout(() => { player.mesh.visible = false; }, 200);
478
- setTimeout(() => { player.mesh.visible = true; }, 300);
479
 
480
  if (player.health <= 0) {
481
  player.health = 0;
482
- scene.remove(player.mesh);
483
- document.body.removeChild(player.healthBar.container);
484
- if(players.every(p => p.health <= 0)) {
485
- endGame("The Qotile has defeated all Yars!");
486
- }
487
  }
488
  updateUI(); return;
489
  }
490
  }
491
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
492
 
493
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
494
  function updateQotile(delta) {
495
- if (qotile && qotile.mesh) {
496
  const time = clock.getElapsedTime();
497
  qotile.mesh.rotation.y += delta * 0.1;
498
  qotile.mesh.userData.eyes.forEach((eye, i) => {
@@ -503,58 +676,76 @@
503
  qotile.hitTimer -= delta;
504
  if (qotile.hitTimer <= 0) qotile.mesh.children[0].material.emissive.setHex(0x550000);
505
  }
506
- if (qotile.health > 0) {
507
  qotile.spawnCooldownTimer -= delta;
508
- if (qotile.spawnCooldownTimer <= 0) {
509
- createBabyYar(qotile.mesh.position);
510
- qotile.spawnCooldownTimer = gameSettings.qotileSpawnCooldown;
511
- }
512
  }
513
  }
514
  }
515
-
516
- function updatePlayers(delta) { /* ... unchanged wing animation ... */
517
- const time = clock.getElapsedTime();
518
- players.forEach(player => {
519
- if (player.health <= 0) return;
520
- const wings = player.mesh.userData.wings;
521
- const flapSpeed = player.mesh.userData.isMoving ? 15 : 4;
522
- wings[0].rotation.y = Math.sin(time * flapSpeed) * 0.8;
523
- wings[1].rotation.y = -Math.sin(time * flapSpeed) * 0.8;
524
- wings[2].rotation.y = Math.sin(time * flapSpeed + 0.5) * 0.6;
525
- wings[3].rotation.y = -Math.sin(time * flapSpeed + 0.5) * 0.6;
526
- });
527
- }
528
 
529
  function updateUI() {
530
  const p1 = players[0] || { score: 0 };
531
  const p2 = players[1] || { score: 0 };
532
  document.getElementById('player1Info').textContent = `Player 1 (WASD, E): Score ${p1.score}`;
533
  document.getElementById('player2Info').textContent = `Player 2 (IJKL, U): Score ${p2.score}`;
534
-
535
- // Health bars are updated in the main loop
536
  }
537
-
538
- function endGame(message) {
539
- if (!gameActive) return;
540
- gameActive = false;
541
- document.getElementById('gameOverMessage').textContent = message;
542
- document.getElementById('gameOverScreen').style.display = 'flex';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
543
  }
544
 
545
  function restartGame() {
546
- // Remove all dynamic elements
 
 
 
 
 
547
  [...players, ...babyYars].forEach(char => {
548
- if (char.mesh) scene.remove(char.mesh);
549
- if (char.healthBar) document.body.removeChild(char.healthBar.container);
 
 
 
 
 
550
  });
551
  if (qotile) {
552
- if(qotile.mesh) scene.remove(qotile.mesh);
553
- if(qotile.healthBar) document.body.removeChild(qotile.healthBar.container);
 
 
554
  }
555
- playerProjectiles.forEach(p => { if (p) scene.remove(p); });
556
-
557
- players = []; playerProjectiles = []; babyYars = [];
558
 
559
  createPlayers();
560
  createNeutralZone();
@@ -564,32 +755,25 @@
564
  document.getElementById('gameOverScreen').style.display = 'none';
565
  updateUI();
566
  }
567
-
568
- function animate() {
569
- requestAnimationFrame(animate);
570
- const delta = clock.getDelta();
571
- if (gameActive) {
572
- players.forEach(player => {
573
- if (player.health > 0) {
574
- handlePlayerMovement(player, delta);
575
- handlePlayerShooting(player, delta);
576
- }
577
- updateHealthBar(player);
578
- });
579
- babyYars.forEach(updateHealthBar);
580
- if (qotile) updateHealthBar(qotile);
581
-
582
- updateProjectilesAndMinions(delta);
583
- updateQotile(delta);
584
- updatePlayers(delta);
585
- }
586
- renderer.render(scene, camera);
587
  }
588
 
589
  function onKeyDown(event) { keysPressed[event.code] = true; }
590
  function onKeyUp(event) { keysPressed[event.code] = false; }
591
- function onWindowResize() { /* ... unchanged ... */ }
592
- window.onload = function() { init(); };
 
 
 
 
 
 
 
593
  </script>
594
  </body>
595
  </html>
 
25
  .player-info { padding: 5px; border-radius: 4px; }
26
  .player1 { background-color: rgba(0, 150, 255, 0.5); }
27
  .player2 { background-color: rgba(255, 100, 0, 0.5); }
28
+ .status-bar-container {
29
  position: absolute;
30
  width: 100px;
31
  height: 16px;
 
41
  z-index: 20;
42
  display: none; /* Hidden by default, controlled by JS */
43
  }
44
+ .status-bar-fill {
45
  height: 100%;
 
46
  width: 100%; /* Full by default */
47
  transition: width 0.2s ease-in-out;
48
  }
49
  #gameOverScreen {
50
+ position: absolute; top: 50%; left: 50%;
 
51
  transform: translate(-50%, -50%);
52
+ padding: 30px; background-color: rgba(20, 20, 20, 0.9);
53
+ border: 2px solid #555; border-radius: 15px;
54
+ text-align: center; display: none; z-index: 100;
 
 
 
 
55
  }
56
  #gameOverScreen h2 { margin-top: 0; font-size: 28px; color: #ff4444; }
57
  #gameOverScreen p { font-size: 18px; }
 
78
  let scene, camera, renderer, clock;
79
  let players = [];
80
  let playerProjectiles = [];
81
+ let enemyProjectiles = [];
82
+ let babyYars = [];
83
  let neutralZoneBlocks = [];
84
  let qotile;
85
+ let zergState = {
86
+ active: false,
87
+ timer: 0,
88
+ mesh: null,
89
+ shootCooldownTimer: 0
90
+ };
91
 
92
  const keysPressed = {};
93
  const gameSettings = {
94
  playerSpeed: 10,
95
  projectileSpeed: 30,
96
+ babyYarSpeed: 6,
97
  projectileSize: 0.2,
98
  neutralZoneBlockSize: 2,
99
  qotileSize: 4,
100
+ playAreaWidth: 40,
101
  playAreaHeight: 30,
102
  playerShootCooldown: 0.2,
103
+ qotileSpawnCooldown: 2.5,
104
+ playerInitialHealth: 150,
105
+ playerInitialEnergy: 100,
106
+ energyRechargeRate: 5, // per second
107
+ zergActivationCost: 80,
108
+ zergMergeDistance: 4,
109
+ zergDuration: 5, // 5 seconds
110
+ zergShootCooldown: 0.15,
111
+ qotileInitialHealth: 250,
112
+ babyYarInitialHealth: 5,
113
+ babyYarDamage: 15,
114
+ babyYarEyeballDamage: 30,
115
+ babyYarAttackDistance: 12,
116
  pointsPerNeutralBlock: 10,
117
  pointsPerBabyYar: 25,
118
+ pointsPerQotileHit: 10,
119
  };
120
  let gameActive = true;
121
 
122
+ function disposeObject(obj) {
123
+ if (!obj) return;
124
+ if (obj.traverse) {
125
+ obj.traverse(child => {
126
+ if (child.isMesh) {
127
+ if (child.geometry) child.geometry.dispose();
128
+ if (child.material) {
129
+ if (Array.isArray(child.material)) {
130
+ child.material.forEach(material => material.dispose());
131
+ } else {
132
+ child.material.dispose();
133
+ }
134
+ }
135
+ }
136
+ });
137
+ }
138
+ if (obj.parent) obj.parent.remove(obj);
139
+ }
140
+
141
+ function createStatusBar(character, type) {
142
  const container = document.createElement('div');
143
+ container.className = 'status-bar-container';
 
144
  const fill = document.createElement('div');
145
+ fill.className = 'status-bar-fill';
 
146
  container.appendChild(fill);
147
  document.body.appendChild(container);
148
 
149
+ if (type === 'health') {
150
+ character.healthBar = { container, fill, text: container };
151
+ } else { // energy
152
+ character.energyBar = { container, fill, text: container };
153
+ }
154
+
155
+ // Styling
156
+ if (type === 'health') {
157
+ if (character.isPlayer) fill.style.backgroundColor = '#2196F3';
158
+ else if (character.isBabyYar) fill.style.backgroundColor = '#f44336';
159
+ else { /*Qotile*/
160
+ fill.style.backgroundColor = '#ff9800';
161
+ container.style.width = '200px'; container.style.height = '20px';
162
+ container.style.fontSize = '14px'; container.style.lineHeight = '18px';
163
+ }
164
+ } else { // energy
165
+ fill.style.backgroundColor = '#BA68C8'; // Purple for energy
166
+ container.style.height = '8px';
167
  }
168
  }
169
 
170
+ function updateStatusBar(character, type) {
171
+ const barData = type === 'health' ? character.healthBar : character.energyBar;
172
+ if (!barData) return;
173
+
174
+ const currentValue = type === 'health' ? character.health : character.energy;
175
+ const maxValue = type === 'health' ? character.maxHealth : character.maxEnergy;
176
+
177
+ if (currentValue <= 0 && type !== 'energy') {
178
+ barData.container.style.display = 'none';
179
+ return;
180
+ }
181
+
182
+ const percent = (currentValue / maxValue) * 100;
183
+ barData.fill.style.width = `${percent}%`;
184
+
185
+ if (type === 'health') {
186
+ barData.text.textContent = `${Math.ceil(currentValue)} / ${maxValue}`;
187
+ } else {
188
+ barData.text.textContent = ''; // No text for energy bar
189
+ }
190
 
 
191
  const screenPosition = toScreenPosition(character.mesh, camera);
192
+ if (screenPosition.z > 1 || !character.mesh.visible) {
193
+ barData.container.style.display = 'none';
194
  } else {
195
+ barData.container.style.display = 'block';
196
+ const yOffset = type === 'health' ? -40 : -25; // Position energy bar below health bar
197
+ barData.container.style.left = `${screenPosition.x}px`;
198
+ barData.container.style.top = `${screenPosition.y + yOffset}px`;
199
  }
200
  }
201
+
202
  function toScreenPosition(obj, camera) {
203
  const vector = new THREE.Vector3();
204
+ if(!obj || !obj.matrixWorld) return {x:0, y:0, z:1};
205
  obj.updateMatrixWorld();
206
  vector.setFromMatrixPosition(obj.matrixWorld);
207
  vector.project(camera);
 
208
  vector.x = (vector.x * window.innerWidth / 2) + window.innerWidth / 2;
209
  vector.y = -(vector.y * window.innerHeight / 2) + window.innerHeight / 2;
 
210
  return vector;
211
  }
212
 
 
213
  function init() {
214
  gameActive = true;
215
  scene = new THREE.Scene();
 
238
  animate();
239
  }
240
 
241
+ function createYarModel(color) {
242
  const yarGroup = new THREE.Group();
243
  const bodyGeometry = new THREE.CylinderGeometry(0.2, 0.4, 1.5, 8);
244
  const bodyMaterial = new THREE.MeshStandardMaterial({ color: color, roughness: 0.5 });
 
255
  const wingMaterial = new THREE.MeshStandardMaterial({ color: color, transparent: true, opacity: 0.7, side: THREE.DoubleSide });
256
  const wings = [];
257
  for (let i = 0; i < 4; i++) {
258
+ const wing = new THREE.Mesh(wingGeometry, wingMaterial.clone());
259
  wing.position.y = 0.2;
260
  yarGroup.add(wing);
261
  wings.push(wing);
 
268
  yarGroup.userData.isMoving = false;
269
  return yarGroup;
270
  }
271
+
272
  function createPlayers() {
 
273
  players = [];
274
  playerProjectiles = [];
275
 
 
280
  mesh: p1Model, isPlayer: true, isPlayer2: false,
281
  controls: { up: 'KeyW', down: 'KeyS', left: 'KeyA', right: 'KeyD', shoot: 'KeyE' },
282
  shootCooldownTimer: 0, score: 0,
283
+ health: gameSettings.playerInitialHealth, maxHealth: gameSettings.playerInitialHealth,
284
+ energy: gameSettings.playerInitialEnergy, maxEnergy: gameSettings.playerInitialEnergy
285
  };
286
+ createStatusBar(p1, 'health');
287
+ createStatusBar(p1, 'energy');
288
  players.push(p1);
289
 
290
  const p2Model = createYarModel(0xff6600);
 
294
  mesh: p2Model, isPlayer: true, isPlayer2: true,
295
  controls: { up: 'KeyI', down: 'KeyK', left: 'KeyJ', right: 'KeyL', shoot: 'KeyU' },
296
  shootCooldownTimer: 0, score: 0,
297
+ health: gameSettings.playerInitialHealth, maxHealth: gameSettings.playerInitialHealth,
298
+ energy: gameSettings.playerInitialEnergy, maxEnergy: gameSettings.playerInitialEnergy
299
  };
300
+ createStatusBar(p2, 'health');
301
+ createStatusBar(p2, 'energy');
302
  players.push(p2);
303
  }
304
 
305
+ function createQotileModel() {
306
  const qotileGroup = new THREE.Group();
307
  const coreGeometry = new THREE.DodecahedronGeometry(gameSettings.qotileSize, 0);
308
  const coreMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000, emissive: 0x550000, roughness: 0.2 });
 
312
  const eyeMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });
313
  const pupilGeometry = new THREE.SphereGeometry(0.3, 12, 12);
314
  const pupilMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 });
315
+
316
+ const leftEye = new THREE.Mesh(eyeGeometry, eyeMaterial.clone());
317
+ leftEye.position.set(-1.2, 1, 3.5);
318
+ const rightEye = new THREE.Mesh(eyeGeometry, eyeMaterial.clone());
319
+ rightEye.position.set(1.2, 1, 3.5);
320
+
321
  const leftPupil = new THREE.Mesh(pupilGeometry, pupilMaterial);
322
+ leftPupil.position.z = 0.6;
323
+ const rightPupil = new THREE.Mesh(pupilGeometry, pupilMaterial.clone());
324
+ rightPupil.position.z = 0.6;
325
+
326
  leftEye.add(leftPupil);
327
  rightEye.add(rightPupil);
328
  qotileGroup.add(leftEye);
 
331
  return qotileGroup;
332
  }
333
 
334
+ function createNeutralZone() {
335
+ neutralZoneBlocks.forEach(block => disposeObject(block));
 
 
 
336
  neutralZoneBlocks = [];
337
  const blockGeometry = new THREE.BoxGeometry(gameSettings.neutralZoneBlockSize, gameSettings.neutralZoneBlockSize, gameSettings.neutralZoneBlockSize / 2);
338
  const blockMaterial = new THREE.MeshStandardMaterial({ color: 0x888888, roughness: 0.7, metalness: 0.3 });
 
348
  }
349
 
350
  function createQotile() {
 
 
 
 
351
  const qotileModel = createQotileModel();
352
  qotileModel.position.set(0, 0, -20);
353
  scene.add(qotileModel);
 
356
  health: gameSettings.qotileInitialHealth, maxHealth: gameSettings.qotileInitialHealth,
357
  hitTimer: 0, spawnCooldownTimer: gameSettings.qotileSpawnCooldown,
358
  };
359
+ createStatusBar(qotile, 'health');
360
  }
361
 
 
362
  function createBabyYar(startPosition) {
363
+ const group = createQotileModel();
364
  group.scale.set(0.2, 0.2, 0.2);
365
  group.position.copy(startPosition);
366
+ group.lookAt(new THREE.Vector3(0,0,100));
367
 
368
  const baby = {
369
  mesh: group, isBabyYar: true,
370
  health: gameSettings.babyYarInitialHealth, maxHealth: gameSettings.babyYarInitialHealth,
371
+ velocity: new THREE.Vector3(),
372
+ hasAttacked: false
373
  };
374
 
375
+ createStatusBar(baby, 'health');
376
  scene.add(group);
377
  babyYars.push(baby);
378
  }
379
 
380
+ function handlePlayerMovement(player, delta) {
381
  let moved = false;
382
  const moveDistance = gameSettings.playerSpeed * delta;
383
  if (keysPressed[player.controls.up]) { player.mesh.position.y += moveDistance; moved = true; }
 
391
  player.mesh.position.y = Math.max(-halfHeight, Math.min(halfHeight, player.mesh.position.y));
392
  }
393
 
394
+ function handlePlayerShooting(player, delta) {
395
  if (player.shootCooldownTimer > 0) player.shootCooldownTimer -= delta;
396
  if (keysPressed[player.controls.shoot] && player.shootCooldownTimer <= 0) {
397
  player.shootCooldownTimer = gameSettings.playerShootCooldown;
398
  createProjectile(player);
399
  }
400
  }
401
+
402
+ function createProjectile(player) {
403
  const projectileGeometry = new THREE.SphereGeometry(gameSettings.projectileSize, 8, 8);
404
  const projectileMaterial = new THREE.MeshBasicMaterial({ color: player.isPlayer2 ? 0xffaa33 : 0x66ccff });
405
  const projectile = new THREE.Mesh(projectileGeometry, projectileMaterial);
 
416
  for (let i = playerProjectiles.length - 1; i >= 0; i--) {
417
  const p = playerProjectiles[i];
418
  p.position.addScaledVector(p.userData.velocity, delta);
419
+ if (p.position.z < -40) {
420
+ disposeObject(p);
421
+ playerProjectiles.splice(i, 1);
422
+ continue;
423
  }
424
  checkPlayerProjectileCollision(p, i);
425
  }
426
 
427
+ // Enemy (eyeball) projectiles
428
+ for (let i = enemyProjectiles.length - 1; i >= 0; i--) {
429
+ const p = enemyProjectiles[i];
430
+ p.position.addScaledVector(p.userData.velocity, delta);
431
+ const pSphere = new THREE.Sphere(p.position, 0.4);
432
+ for (let j = players.length - 1; j >= 0; j--) {
433
+ const player = players[j];
434
+ if (player.health <= 0) continue;
435
+ const playerBox = new THREE.Box3().setFromObject(player.mesh);
436
+ if (pSphere.intersectsBox(playerBox)) {
437
+ player.health -= gameSettings.babyYarEyeballDamage;
438
+ disposeObject(p);
439
+ enemyProjectiles.splice(i, 1);
440
+ // Player hit effect
441
+ player.mesh.visible = false;
442
+ setTimeout(() => { if(player.mesh) player.mesh.visible = true; }, 100);
443
+ setTimeout(() => { if(player.mesh) player.mesh.visible = false; }, 200);
444
+ setTimeout(() => { if(player.mesh) player.mesh.visible = true; }, 300);
445
+ if (player.health <= 0) {
446
+ player.health = 0;
447
+ if (player.healthBar) document.body.removeChild(player.healthBar.container);
448
+ if (player.energyBar) document.body.removeChild(player.energyBar.container);
449
+ disposeObject(player.mesh);
450
+ if(players.every(p => p.health <= 0)) endGame("The Qotile has defeated all Yars!");
451
+ }
452
+ updateUI();
453
+ break;
454
+ }
455
+ }
456
+ if (p.position.z > 30) { disposeObject(p); enemyProjectiles.splice(i, 1); }
457
+ }
458
+
459
+ // Baby Yars AI
460
  for (let i = babyYars.length - 1; i >= 0; i--) {
461
  const baby = babyYars[i];
462
+ let nearestPlayer = null;
463
+ let minDistance = Infinity;
464
+ players.forEach(p => {
465
+ if (p.health > 0) {
466
+ const distance = baby.mesh.position.distanceTo(p.mesh.position);
467
+ if (distance < minDistance) {
468
+ minDistance = distance;
469
+ nearestPlayer = p;
470
+ }
471
+ }
472
  });
473
 
474
+ if (nearestPlayer) {
475
+ const direction = new THREE.Vector3().subVectors(nearestPlayer.mesh.position, baby.mesh.position).normalize();
476
+ baby.velocity.lerp(direction, 0.05);
477
+ baby.mesh.position.addScaledVector(baby.velocity, gameSettings.babyYarSpeed * delta);
478
+ baby.mesh.lookAt(nearestPlayer.mesh.position);
479
+
480
+ if (!baby.hasAttacked && minDistance < gameSettings.babyYarAttackDistance) {
481
+ baby.hasAttacked = true;
482
+ baby.mesh.userData.eyes.forEach(eye => {
483
+ if (!eye) return;
484
+ const worldPos = new THREE.Vector3();
485
+ eye.getWorldPosition(worldPos);
486
+
487
+ const eyeballProjectile = new THREE.Mesh(eye.children[0].geometry.clone(), eye.children[0].material.clone());
488
+ eyeballProjectile.position.copy(worldPos);
489
+ const shootDirection = new THREE.Vector3().subVectors(nearestPlayer.mesh.position, worldPos).normalize();
490
+ eyeballProjectile.userData = { velocity: shootDirection.multiplyScalar(gameSettings.projectileSpeed) };
491
+ scene.add(eyeballProjectile);
492
+ enemyProjectiles.push(eyeballProjectile);
493
+ });
494
+
495
+ disposeObject(baby.mesh);
496
+ if (baby.healthBar) document.body.removeChild(baby.healthBar.container);
497
+ babyYars.splice(i, 1);
498
+ continue;
499
+ }
500
  }
501
+
502
  checkBabyYarCollision(baby, i);
503
  }
504
  }
 
507
  const pSphere = new THREE.Sphere(projectile.position, gameSettings.projectileSize);
508
  const owner = projectile.userData.owner;
509
 
510
+ for (let i = neutralZoneBlocks.length - 1; i >= 0; i--) {
511
+ const block = neutralZoneBlocks[i];
512
+ const blockBox = new THREE.Box3().setFromObject(block);
513
+ if (pSphere.intersectsBox(blockBox)) {
514
+ disposeObject(block);
515
+ neutralZoneBlocks.splice(i,1);
516
+ disposeObject(projectile);
517
+ playerProjectiles.splice(projectileIndex, 1);
518
+ if (owner) owner.score += gameSettings.pointsPerNeutralBlock;
519
+ updateUI();
520
+ return;
521
+ }
522
+ }
523
+
524
  for (let i = babyYars.length - 1; i >= 0; i--) {
525
  const baby = babyYars[i];
526
  const babyBox = new THREE.Box3().setFromObject(baby.mesh);
527
  if (pSphere.intersectsBox(babyBox)) {
 
528
  const damage = Math.floor(Math.random() * 4) + 1;
529
  baby.health -= damage;
530
 
531
  if (baby.health <= 0) {
532
+ if (owner) owner.score += gameSettings.pointsPerBabyYar;
533
+ disposeObject(baby.mesh);
534
+ if(baby.healthBar) document.body.removeChild(baby.healthBar.container);
535
  babyYars.splice(i, 1);
536
  }
537
 
538
+ disposeObject(projectile);
539
  playerProjectiles.splice(projectileIndex, 1);
540
  updateUI(); return;
541
  }
542
  }
543
 
 
544
  if (qotile && qotile.health > 0) {
545
  const qotileBox = new THREE.Box3().setFromObject(qotile.mesh);
546
  if (pSphere.intersectsBox(qotileBox)) {
547
  const damage = Math.floor(Math.random() * 4) + 1;
548
  qotile.health -= damage;
549
+ if (owner) owner.score += gameSettings.pointsPerQotileHit;
550
  qotile.mesh.children[0].material.emissive.setHex(0xffffff);
551
  qotile.hitTimer = 0.1;
552
  if (qotile.health <= 0) {
553
+ endGame((owner && owner.isPlayer2 ? "Player 2" : "Player 1") + " destroyed the Qotile!");
554
  }
555
+ disposeObject(projectile);
556
  playerProjectiles.splice(projectileIndex, 1);
557
  updateUI(); return;
558
  }
559
  }
560
  }
561
+
 
562
  function checkBabyYarCollision(baby, babyIndex) {
563
+ if (!baby || !baby.mesh) return;
564
+ const babySphere = new THREE.Sphere(baby.mesh.position, 1.0);
565
  for(let i = players.length - 1; i >= 0; i--) {
566
  const player = players[i];
567
  if (player.health <= 0) continue;
568
  const playerBox = new THREE.Box3().setFromObject(player.mesh);
569
+ if (player.mesh.visible && babySphere.intersectsBox(playerBox)) {
 
570
  player.health -= gameSettings.babyYarDamage;
571
+ disposeObject(baby.mesh);
572
+ if (baby.healthBar) document.body.removeChild(baby.healthBar.container);
 
 
573
  babyYars.splice(babyIndex, 1);
574
 
 
575
  player.mesh.visible = false;
576
+ setTimeout(() => { if(player.mesh) player.mesh.visible = true; }, 100);
577
+ setTimeout(() => { if(player.mesh) player.mesh.visible = false; }, 200);
578
+ setTimeout(() => { if(player.mesh) player.mesh.visible = true; }, 300);
579
 
580
  if (player.health <= 0) {
581
  player.health = 0;
582
+ if (player.healthBar) document.body.removeChild(player.healthBar.container);
583
+ if (player.energyBar) document.body.removeChild(player.energyBar.container);
584
+ disposeObject(player.mesh);
585
+ if(players.every(p => p.health <= 0)) endGame("The Qotile has defeated all Yars!");
 
586
  }
587
  updateUI(); return;
588
  }
589
  }
590
  }
591
+
592
+ function handleZergMode(delta) {
593
+ if (keysPressed['Space'] && !zergState.active && players.length === 2) {
594
+ const p1 = players.find(p => !p.isPlayer2);
595
+ const p2 = players.find(p => p.isPlayer2);
596
+ if (p1 && p2 && p1.health > 0 && p2.health > 0) {
597
+ const distance = p1.mesh.position.distanceTo(p2.mesh.position);
598
+ if (distance < gameSettings.zergMergeDistance &&
599
+ p1.energy >= gameSettings.zergActivationCost &&
600
+ p2.energy >= gameSettings.zergActivationCost) {
601
+
602
+ p1.energy -= gameSettings.zergActivationCost;
603
+ p2.energy -= gameSettings.zergActivationCost;
604
+
605
+ zergState.active = true;
606
+ zergState.timer = gameSettings.zergDuration;
607
+
608
+ const zergModel = createYarModel(0xBA68C8);
609
+ zergModel.scale.set(1.5, 1.5, 1.5);
610
+ const midPoint = new THREE.Vector3().addVectors(p1.mesh.position, p2.mesh.position).multiplyScalar(0.5);
611
+ zergModel.position.copy(midPoint);
612
+ zergState.mesh = zergModel;
613
+ scene.add(zergModel);
614
+
615
+ p1.mesh.visible = false;
616
+ p2.mesh.visible = false;
617
+ }
618
+ }
619
+ }
620
 
621
+ if (zergState.active) {
622
+ zergState.timer -= delta;
623
+ zergState.shootCooldownTimer -= delta;
624
+
625
+ if (zergState.shootCooldownTimer <= 0) {
626
+ zergState.shootCooldownTimer = gameSettings.zergShootCooldown;
627
+ const angles = [-0.2, -0.1, 0, 0.1, 0.2];
628
+ angles.forEach(angle => {
629
+ const p = new THREE.Mesh(new THREE.SphereGeometry(0.3, 8, 8), new THREE.MeshBasicMaterial({color: 0xBA68C8}));
630
+ p.position.copy(zergState.mesh.position);
631
+ p.position.z -= 1.5;
632
+ const velocity = new THREE.Vector3(Math.sin(angle), 0, -1).normalize().multiplyScalar(gameSettings.projectileSpeed);
633
+ p.userData = { owner: null, velocity: velocity }; // No specific owner
634
+ scene.add(p);
635
+ playerProjectiles.push(p);
636
+ });
637
+ }
638
+
639
+ if (zergState.timer <= 0) {
640
+ zergState.active = false;
641
+ disposeObject(zergState.mesh);
642
+ zergState.mesh = null;
643
+ players.forEach(p => { if (p.health > 0) p.mesh.visible = true; });
644
+ }
645
+ }
646
+ }
647
+
648
+ function updatePlayers(delta) {
649
+ players.forEach(player => {
650
+ if (player.health > 0) {
651
+ if (!zergState.active) {
652
+ player.energy = Math.min(player.maxEnergy, player.energy + gameSettings.energyRechargeRate * delta);
653
+ }
654
+ if (player.mesh.visible) {
655
+ const time = clock.getElapsedTime();
656
+ const wings = player.mesh.userData.wings;
657
+ const flapSpeed = player.mesh.userData.isMoving ? 15 : 4;
658
+ wings[0].rotation.y = Math.sin(time * flapSpeed) * 0.8;
659
+ wings[1].rotation.y = -Math.sin(time * flapSpeed) * 0.8;
660
+ wings[2].rotation.y = Math.sin(time * flapSpeed + 0.5) * 0.6;
661
+ wings[3].rotation.y = -Math.sin(time * flapSpeed + 0.5) * 0.6;
662
+ }
663
+ }
664
+ });
665
+ }
666
+
667
  function updateQotile(delta) {
668
+ if (qotile && qotile.health > 0) {
669
  const time = clock.getElapsedTime();
670
  qotile.mesh.rotation.y += delta * 0.1;
671
  qotile.mesh.userData.eyes.forEach((eye, i) => {
 
676
  qotile.hitTimer -= delta;
677
  if (qotile.hitTimer <= 0) qotile.mesh.children[0].material.emissive.setHex(0x550000);
678
  }
679
+ if (qotile.spawnCooldownTimer > 0) {
680
  qotile.spawnCooldownTimer -= delta;
681
+ } else {
682
+ createBabyYar(qotile.mesh.position);
683
+ qotile.spawnCooldownTimer = gameSettings.qotileSpawnCooldown;
 
684
  }
685
  }
686
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
687
 
688
  function updateUI() {
689
  const p1 = players[0] || { score: 0 };
690
  const p2 = players[1] || { score: 0 };
691
  document.getElementById('player1Info').textContent = `Player 1 (WASD, E): Score ${p1.score}`;
692
  document.getElementById('player2Info').textContent = `Player 2 (IJKL, U): Score ${p2.score}`;
 
 
693
  }
694
+
695
+ function animate() {
696
+ requestAnimationFrame(animate);
697
+ const delta = clock.getDelta();
698
+ if (gameActive) {
699
+ if (!zergState.active) {
700
+ players.forEach(player => {
701
+ if (player.health > 0) {
702
+ handlePlayerMovement(player, delta);
703
+ handlePlayerShooting(player, delta);
704
+ }
705
+ });
706
+ }
707
+
708
+ handleZergMode(delta);
709
+
710
+ players.forEach(p => {
711
+ updateStatusBar(p, 'health');
712
+ updateStatusBar(p, 'energy');
713
+ });
714
+ babyYars.forEach(b => updateStatusBar(b, 'health'));
715
+ if (qotile) updateStatusBar(qotile, 'health');
716
+
717
+ updateProjectilesAndMinions(delta);
718
+ updateQotile(delta);
719
+ updatePlayers(delta);
720
+ }
721
+ renderer.render(scene, camera);
722
  }
723
 
724
  function restartGame() {
725
+ if (zergState.active) {
726
+ zergState.active = false;
727
+ disposeObject(zergState.mesh);
728
+ zergState.mesh = null;
729
+ }
730
+
731
  [...players, ...babyYars].forEach(char => {
732
+ if (char.mesh) disposeObject(char.mesh);
733
+ if (char.healthBar && char.healthBar.container.parentNode) {
734
+ document.body.removeChild(char.healthBar.container);
735
+ }
736
+ if (char.energyBar && char.energyBar.container.parentNode) {
737
+ document.body.removeChild(char.energyBar.container);
738
+ }
739
  });
740
  if (qotile) {
741
+ if(qotile.mesh) disposeObject(qotile.mesh);
742
+ if(qotile.healthBar && qotile.healthBar.container.parentNode) {
743
+ document.body.removeChild(qotile.healthBar.container);
744
+ }
745
  }
746
+ [...playerProjectiles, ...enemyProjectiles].forEach(p => disposeObject(p));
747
+
748
+ players = []; playerProjectiles = []; babyYars = []; enemyProjectiles = [];
749
 
750
  createPlayers();
751
  createNeutralZone();
 
755
  document.getElementById('gameOverScreen').style.display = 'none';
756
  updateUI();
757
  }
758
+
759
+ function endGame(message) {
760
+ if (!gameActive) return;
761
+ gameActive = false;
762
+ document.getElementById('gameOverMessage').textContent = message;
763
+ document.getElementById('gameOverScreen').style.display = 'flex';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
764
  }
765
 
766
  function onKeyDown(event) { keysPressed[event.code] = true; }
767
  function onKeyUp(event) { keysPressed[event.code] = false; }
768
+ function onWindowResize() {
769
+ camera.aspect = window.innerWidth / window.innerHeight;
770
+ camera.updateProjectionMatrix();
771
+ renderer.setSize(window.innerWidth, window.innerHeight);
772
+ }
773
+
774
+ window.onload = function() {
775
+ init();
776
+ };
777
  </script>
778
  </body>
779
  </html>