awacke1 commited on
Commit
30b7648
·
verified ·
1 Parent(s): 3c35d3c

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +594 -18
index.html CHANGED
@@ -1,19 +1,595 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Yar's Revenge 3D</title>
7
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
8
+ <style>
9
+ body { margin: 0; overflow: hidden; font-family: 'Arial', sans-serif; background-color: #000; color: #fff; }
10
+ canvas { display: block; }
11
+ #infoPanel {
12
+ position: absolute;
13
+ top: 10px;
14
+ left: 10px;
15
+ padding: 10px;
16
+ background-color: rgba(0,0,0,0.7);
17
+ border-radius: 8px;
18
+ color: #fff;
19
+ font-size: 16px;
20
+ display: flex;
21
+ flex-direction: column;
22
+ gap: 8px;
23
+ z-index: 10;
24
+ }
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;
32
+ background-color: #333;
33
+ border: 1px solid #777;
34
+ border-radius: 4px;
35
+ overflow: hidden;
36
+ text-align: center;
37
+ color: white;
38
+ font-size: 12px;
39
+ line-height: 14px;
40
+ transform: translateX(-50%); /* Center horizontally */
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; }
64
+ #gameOverScreen button {
65
+ padding: 12px 25px; font-size: 18px; color: #fff; background-color: #007bff;
66
+ border: none; border-radius: 8px; cursor: pointer; margin-top: 20px;
67
+ transition: background-color 0.3s ease;
68
+ }
69
+ #gameOverScreen button:hover { background-color: #0056b3; }
70
+ </style>
71
+ </head>
72
+ <body>
73
+ <div id="infoPanel">
74
+ <div id="player1Info" class="player-info player1">Player 1 (WASD, E): Score 0</div>
75
+ <div id="player2Info" class="player-info player2">Player 2 (IJKL, U): Score 0</div>
76
+ </div>
77
+ <div id="gameOverScreen">
78
+ <h2>Game Over!</h2>
79
+ <p id="gameOverMessage"></p>
80
+ <button id="restartButton">Restart Game</button>
81
+ </div>
82
+
83
+ <script>
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();
177
+ scene.background = new THREE.Color(0x111122);
178
+ camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
179
+ camera.position.set(0, 8, 30);
180
+ camera.lookAt(0, 0, 0);
181
+ renderer = new THREE.WebGLRenderer({ antialias: true });
182
+ renderer.setSize(window.innerWidth, window.innerHeight);
183
+ document.body.appendChild(renderer.domElement);
184
+ const ambientLight = new THREE.AmbientLight(0x606060);
185
+ scene.add(ambientLight);
186
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
187
+ directionalLight.position.set(5, 10, 7.5);
188
+ scene.add(directionalLight);
189
+ clock = new THREE.Clock();
190
+ createPlayers();
191
+ createNeutralZone();
192
+ createQotile();
193
+ document.addEventListener('keydown', onKeyDown);
194
+ document.addEventListener('keyup', onKeyUp);
195
+ window.addEventListener('resize', onWindowResize);
196
+ document.getElementById('restartButton').addEventListener('click', restartGame);
197
+ document.getElementById('gameOverScreen').style.display = 'none';
198
+ updateUI();
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 });
206
+ const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
207
+ body.rotation.x = Math.PI / 2;
208
+ yarGroup.add(body);
209
+ const headGeometry = new THREE.ConeGeometry(0.3, 0.6, 8);
210
+ const headMaterial = new THREE.MeshStandardMaterial({ color: 0xcccccc, metalness: 0.5 });
211
+ const head = new THREE.Mesh(headGeometry, headMaterial);
212
+ head.position.z = -1;
213
+ head.rotation.x = Math.PI / 2;
214
+ yarGroup.add(head);
215
+ const wingGeometry = new THREE.PlaneGeometry(1.5, 1);
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);
223
+ }
224
+ wings[0].position.set(-0.8, 0, 0); wings[0].rotation.z = Math.PI / 4;
225
+ wings[1].position.set(0.8, 0, 0); wings[1].rotation.z = -Math.PI / 4;
226
+ wings[2].position.set(-0.7, 0, 0.5); wings[2].scale.set(0.8, 0.8, 0.8); wings[2].rotation.z = Math.PI / 4;
227
+ wings[3].position.set(0.7, 0, 0.5); wings[3].scale.set(0.8, 0.8, 0.8); wings[3].rotation.z = -Math.PI / 4;
228
+ yarGroup.userData.wings = wings;
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
+
238
+ const p1Model = createYarModel(0x0099ff);
239
+ p1Model.position.set(-gameSettings.playAreaWidth / 4, 0, 18);
240
+ scene.add(p1Model);
241
+ const p1 = {
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);
251
+ p2Model.position.set(gameSettings.playAreaWidth / 4, 0, 18);
252
+ scene.add(p2Model);
253
+ const p2 = {
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 });
267
+ const core = new THREE.Mesh(coreGeometry, coreMaterial);
268
+ qotileGroup.add(core);
269
+ const eyeGeometry = new THREE.SphereGeometry(0.8, 16, 16);
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);
284
+ qotileGroup.add(rightEye);
285
+ qotileGroup.userData.eyes = [leftEye, rightEye];
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 });
297
+ const numX = Math.floor(gameSettings.playAreaWidth / gameSettings.neutralZoneBlockSize);
298
+ const numY = Math.floor(gameSettings.playAreaHeight / gameSettings.neutralZoneBlockSize);
299
+ for (let i = 0; i < numX; i++) {
300
+ for (let j = 0; j < numY; j++) {
301
+ const block = new THREE.Mesh(blockGeometry.clone(), blockMaterial.clone());
302
+ block.position.set( (i - numX / 2 + 0.5) * gameSettings.neutralZoneBlockSize, (j - numY / 2 + 0.5) * gameSettings.neutralZoneBlockSize, 0);
303
+ scene.add(block); neutralZoneBlocks.push(block);
304
+ }
305
+ }
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);
316
+ qotile = {
317
+ mesh: qotileModel,
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; }
345
+ if (keysPressed[player.controls.down]) { player.mesh.position.y -= moveDistance; moved = true; }
346
+ if (keysPressed[player.controls.left]) { player.mesh.position.x -= moveDistance; moved = true; }
347
+ if (keysPressed[player.controls.right]) { player.mesh.position.x += moveDistance; moved = true; }
348
+ player.mesh.userData.isMoving = moved;
349
+ const halfWidth = gameSettings.playAreaWidth / 2;
350
+ const halfHeight = gameSettings.playAreaHeight / 2;
351
+ player.mesh.position.x = Math.max(-halfWidth, Math.min(halfWidth, player.mesh.position.x));
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);
367
+ const startPosition = player.mesh.position.clone();
368
+ startPosition.z -= 1.5;
369
+ projectile.position.copy(startPosition);
370
+ projectile.userData = { owner: player, velocity: new THREE.Vector3(0, 0, -gameSettings.projectileSpeed) };
371
+ scene.add(projectile);
372
+ playerProjectiles.push(projectile);
373
+ }
374
+
375
+ function updateProjectilesAndMinions(delta) {
376
+ // Player projectiles
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
+ }
408
+
409
+ function checkPlayerProjectileCollision(projectile, projectileIndex) {
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) => {
499
+ eye.children[0].position.x = Math.sin(time * 3 + i) * 0.15;
500
+ eye.children[0].position.y = Math.cos(time * 3 + i) * 0.15;
501
+ });
502
+ if (qotile.hitTimer > 0) {
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();
561
+ createQotile();
562
+
563
+ gameActive = true;
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>