bbc123321 commited on
Commit
e19e778
·
verified ·
1 Parent(s): a5fb081

Manual changes saved

Browse files
Files changed (1) hide show
  1. index.html +451 -509
index.html CHANGED
@@ -1,45 +1,106 @@
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" />
6
- <title>BattleZone Royale - Enhanced</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <script src="https://unpkg.com/feather-icons"></script>
9
  <style>
10
- html, body, #root { height:100%; margin:0; background:#0b1220; color:#fff; font-family:monospace; }
11
- #gameCanvas { display:block; user-select:none; cursor:crosshair; box-shadow:0 0 20px rgba(0,0,0,.5); }
12
- .biome-selector { transition:all .25s ease; }
13
- .biome-selector:hover { transform:scale(1.03); box-shadow:0 0 10px rgba(255,215,0,.15); }
14
- #canvasContainer { position:relative; flex:1; display:flex; justify-content:center; align-items:center; height:100vh; overflow:hidden; }
15
- #stormWarning { z-index:10; }
16
- #deathScreen { z-index:20; }
17
- /* Minimal HUD tweaks */
18
- .hud-pill { background:#111827; padding:.5rem .75rem; border-radius:.5rem; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </style>
20
  </head>
21
  <body>
22
- <div id="landingScreen" class="flex flex-col items-center justify-center h-screen p-6">
23
- <h1 class="text-5xl font-bold text-yellow-400 mb-6 flex items-center">
24
  <i data-feather="target" class="mr-2"></i> BattleZone Royale
25
  </h1>
 
 
 
26
  <div class="grid grid-cols-2 gap-6 max-w-xl w-full">
27
- <div class="biome-selector bg-gray-700 p-6 rounded-lg cursor-pointer hover:bg-yellow-800 flex flex-col items-center" data-biome="desert">
 
 
 
28
  <i data-feather="sun" class="text-yellow-300 mb-2"></i>
29
  <h3 class="font-bold text-lg">Golden Dunes</h3>
30
  <p class="text-xs">High loot, high risk</p>
31
  </div>
32
- <div class="biome-selector bg-gray-700 p-6 rounded-lg cursor-pointer hover:bg-green-800 flex flex-col items-center" data-biome="forest">
 
 
 
33
  <i data-feather="tree" class="text-green-300 mb-2"></i>
34
  <h3 class="font-bold text-lg">Shadow Forest</h3>
35
  <p class="text-xs">Good cover, medium loot</p>
36
  </div>
37
- <div class="biome-selector bg-gray-700 p-6 rounded-lg cursor-pointer hover:bg-blue-800 flex flex-col items-center" data-biome="oasis">
 
 
 
38
  <i data-feather="droplet" class="text-blue-300 mb-2"></i>
39
  <h3 class="font-bold text-lg">Crystal Oasis</h3>
40
  <p class="text-xs">Medium loot, low risk</p>
41
  </div>
42
- <div class="biome-selector bg-gray-700 p-6 rounded-lg cursor-pointer hover:bg-purple-800 flex flex-col items-center" data-biome="ruins">
 
 
 
43
  <i data-feather="layers" class="text-purple-300 mb-2"></i>
44
  <h3 class="font-bold text-lg">Ancient Ruins</h3>
45
  <p class="text-xs">Legendary loot, dangerous</p>
@@ -48,57 +109,138 @@
48
  </div>
49
 
50
  <div id="gameScreen" class="hidden h-screen flex flex-col">
51
- <header class="flex justify-between items-center border-b border-yellow-500 px-6 py-4" style="flex-shrink:0;">
52
- <h1 class="text-3xl font-bold text-yellow-400 flex items-center"><i data-feather="target" class="mr-2"></i>BattleZone Royale</h1>
53
- <div class="flex space-x-4 items-center">
54
- <div class="hud-pill flex items-center space-x-2"><i data-feather="map-pin"></i><span id="currentBiome">-</span></div>
55
- <div class="hud-pill flex items-center space-x-2"><i data-feather="clock"></i><span id="gameTimer">5:00</span></div>
56
- <div class="hud-pill flex items-center space-x-2"><i data-feather="users"></i><span id="playerCount">1/100</span></div>
 
 
 
 
 
 
 
 
 
 
 
57
  </div>
58
  </header>
59
 
60
  <div class="flex flex-1 overflow-hidden">
61
- <div id="canvasContainer" class="relative bg-gray-900 border-4 border-gray-700 rounded-xl max-w-full max-h-full">
 
 
 
 
62
  <canvas id="gameCanvas"></canvas>
63
 
64
- <div id="stormWarning" class="hidden absolute top-6 left-1/2 transform -translate-x-1/2 bg-red-900 bg-opacity-80 text-white px-6 py-3 rounded-lg flex items-center">
 
 
 
 
65
  <i data-feather="alert-circle" class="mr-2"></i>
66
  <span>STORM APPROACHING! MOVE TO SAFE ZONE!</span>
67
  </div>
68
 
69
- <div id="deathScreen" class="hidden absolute inset-0 bg-black bg-opacity-80 flex flex-col items-center justify-center rounded-xl">
 
 
 
 
70
  <i data-feather="skull" class="w-16 h-16 text-red-500 mb-4"></i>
71
  <h2 class="text-4xl font-bold text-red-500 mb-4">YOU DIED!</h2>
72
  <p class="text-xl mb-6">Better luck next time, soldier!</p>
73
- <button id="respawnBtn" class="bg-yellow-500 hover:bg-yellow-600 text-black font-bold py-3 px-6 rounded-lg flex items-center">
 
 
 
74
  <i data-feather="refresh-cw" class="mr-2"></i> Respawn
75
  </button>
76
  </div>
77
  </div>
78
 
79
- <aside class="w-80 bg-gray-800 rounded-xl p-6 overflow-y-auto flex-shrink-0 flex flex-col gap-6" style="max-height:100vh;">
 
 
 
 
 
80
  <section>
81
- <h2 class="text-2xl font-bold text-yellow-400 mb-4 flex items-center"><i data-feather="activity" class="mr-2"></i>Player Stats</h2>
 
 
 
 
82
  <div class="space-y-3">
83
- <div class="flex justify-between"><span>Health:</span><span id="playerHealth" class="font-bold">100%</span></div>
84
- <div class="flex justify-between"><span>Armor:</span><span id="playerArmor" class="font-bold">0%</span></div>
85
- <div class="flex justify-between"><span>Materials:</span><span id="playerMaterials" class="font-bold">0</span></div>
86
- <div class="flex justify-between"><span>Kills:</span><span id="playerKills" class="font-bold text-red-400">0</span></div>
 
 
 
 
 
 
 
 
87
  </div>
88
  </section>
89
 
 
90
  <section>
91
- <h2 class="text-2xl font-bold text-yellow-400 mb-4 flex items-center"><i data-feather="briefcase" class="mr-2"></i>Inventory</h2>
92
- <div id="inventorySlots" class="grid grid-cols-3 gap-2" aria-label="Inventory slots"></div>
 
 
 
 
 
 
 
 
93
  </section>
94
 
 
95
  <section>
96
- <h2 class="text-2xl font-bold text-yellow-400 mb-4 flex items-center"><i data-feather="command" class="mr-2"></i>Controls</h2>
 
 
 
 
97
  <div class="space-y-2 text-sm">
98
- <div class="flex items-center"><span class="bg-gray-700 px-2 py-1 rounded mr-2">WASD</span><span>Move</span></div>
99
- <div class="flex items-center"><span class="bg-gray-700 px-2 py-1 rounded mr-2">Mouse</span><span>Aim & Shoot</span></div>
100
- <div class="flex items-center"><span class="bg-gray-700 px-2 py-1 rounded mr-2">E</span><span>Interact / Loot</span></div>
101
- <div class="flex items-center"><span class="bg-gray-700 px-2 py-1 rounded mr-2">1-5</span><span>Switch weapons</span></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  </div>
103
  </section>
104
  </aside>
@@ -106,581 +248,381 @@
106
  </div>
107
 
108
  <script>
109
- // --- Core setup ---
110
  let gameActive = false;
111
- let selectedBiome = 'forest';
112
-
 
 
 
 
 
 
113
  const canvas = document.getElementById('gameCanvas');
114
  const ctx = canvas.getContext('2d');
115
- const landingScreenEl = document.getElementById('landingScreen');
116
- const gameScreenEl = document.getElementById('gameScreen');
117
  const stormWarningEl = document.getElementById('stormWarning');
118
  const deathScreenEl = document.getElementById('deathScreen');
 
 
119
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  function resizeCanvas() {
121
  const container = document.getElementById('canvasContainer');
122
  canvas.width = container.clientWidth;
123
  canvas.height = container.clientHeight;
124
- cameraUpdate();
 
 
125
  }
126
  window.addEventListener('resize', resizeCanvas);
127
 
128
- // --- World & Camera (large world, camera follows player) ---
129
- const WORLD = { width: 3000, height: 2000 };
130
- const TILE_SIZE = 400; // coarse biome tile size
131
- const camera = { x: 0, y: 0, wobble:0 };
132
- function cameraUpdate() {
133
- camera.x = player.x - canvas.width / 2;
134
- camera.y = player.y - canvas.height / 2;
135
- camera.x = Math.max(0, Math.min(camera.x, WORLD.width - canvas.width));
136
- camera.y = Math.max(0, Math.min(camera.y, WORLD.height - canvas.height));
137
  }
138
 
139
- // --- Player ---
140
- const player = {
141
- x: 0, y: 0, radius: 16, angle: 0, speed: 220, health: 100, armor: 0, kills: 0, materials: 0,
142
- currentWeaponIndex: 0, weapons: [], lastShot:0
143
- };
 
144
 
145
- // --- Input ---
146
- const keys = { w:false,a:false,s:false,d:false, e:false };
147
- const mouse = { canvasX:0, canvasY:0, worldX:0, worldY:0, down:false };
 
 
 
 
 
 
 
 
 
 
148
 
 
149
  window.addEventListener('keydown', (e) => {
150
  if (!gameActive) return;
151
- const k = e.key.toLowerCase();
152
- if (keys.hasOwnProperty(k)) keys[k]=true;
153
- if (k === '1' || k==='2' || k==='3' || k==='4' || k==='5') {
154
- const idx = parseInt(k)-1;
155
- if (player.weapons[idx]) player.currentWeaponIndex = idx;
156
  }
157
  });
158
  window.addEventListener('keyup', (e) => {
159
  if (!gameActive) return;
160
- const k = e.key.toLowerCase();
161
- if (keys.hasOwnProperty(k)) keys[k]=false;
162
- if (k==='e') {
163
- // handled in loop interact
164
  }
165
  });
166
 
 
167
  canvas.addEventListener('mousemove', (e) => {
168
  const rect = canvas.getBoundingClientRect();
169
  mouse.canvasX = e.clientX - rect.left;
170
  mouse.canvasY = e.clientY - rect.top;
171
- mouse.worldX = mouse.canvasX + camera.x;
172
- mouse.worldY = mouse.canvasY + camera.y;
173
- const dx = mouse.worldX - player.x;
174
- const dy = mouse.worldY - player.y;
175
  player.angle = Math.atan2(dy, dx);
176
  });
177
 
178
- canvas.addEventListener('mousedown', (e) => { if (gameActive) mouse.down = true; });
179
- window.addEventListener('mouseup', (e) => { mouse.down = false; });
180
-
181
- // --- Storm ---
182
- const storm = { maxRadius: 900, radius: 900, centerX: WORLD.width/2, centerY: WORLD.height/2, damagePerSecond:1, closingSpeed: 8, active:false };
183
- let gameTime = 300, timerInterval=null;
184
-
185
- // --- Entities: bullets, chests, breakables ---
186
- const bullets = [];
187
- const chests = [];
188
- const objects = []; // breakables
189
-
190
- // --- Simple biome map generation ---
191
- function biomeAt(x,y) {
192
- const bx = Math.floor(x / TILE_SIZE);
193
- const by = Math.floor(y / TILE_SIZE);
194
- const seed = (bx*73856093) ^ (by*19349663);
195
- const r = Math.abs(Math.sin(seed)) % 1;
196
- if (r < 0.25) return 'desert';
197
- if (r < 0.55) return 'forest';
198
- if (r < 0.75) return 'oasis';
199
- return 'ruins';
200
- }
201
-
202
- // --- Populate world with chests and breakables ---
203
- function populateWorld() {
204
- chests.length = 0;
205
- objects.length = 0;
206
-
207
- function rand(min,max){ return Math.random()*(max-min)+min; }
208
-
209
- // Place chests randomly across the world
210
- for (let i=0;i<150;i++){
211
- const x = rand(100, WORLD.width-100);
212
- const y = rand(100, WORLD.height-100);
213
- chests.push({ x,y, opened:false, openProgress:0, loot: generateLootForBiome(biomeAt(x,y)) });
214
- }
215
-
216
- // Place breakable objects
217
- for (let i=0;i<400;i++){
218
- const typeRand = Math.random();
219
- let type='wood';
220
- if (typeRand>0.75) type='stone';
221
- if (typeRand>0.95) type='wall';
222
- const x = rand(50, WORLD.width-50);
223
- const y = rand(50, WORLD.height-50);
224
- const hp = type==='wood'? 40 : (type==='stone'? 80:120);
225
- objects.push({ x,y, type, hp, maxHp:hp });
226
  }
227
- }
228
-
229
- function generateLootForBiome(biome) {
230
- const roll = Math.random();
231
- if (roll < 0.4) return { type:'medkit', heal:25 };
232
- if (roll < 0.75) return { type:'materials', amount: randInt(5,20) };
233
- // weapon loot
234
- const weapons = [
235
- { name:'Pistol', dmg:12, rate:500, color:'#ffd86b' },
236
- { name:'SMG', dmg:6, rate:120, color:'#8ef0ff' },
237
- { name:'Shotgun', dmg:22, rate:800, color:'#ff9fb8' },
238
- { name:'Rifle', dmg:18, rate:400, color:'#c7ff9a' },
239
- ];
240
- const w = weapons[randInt(0,weapons.length-1)];
241
- return { type:'weapon', weapon: w };
242
- }
243
-
244
- function randInt(min,max) { return Math.floor(Math.random()*(max-min))+min; }
245
 
246
- // --- Inventory UI ---
247
- function initInventory() {
248
- const inventorySlots = document.getElementById('inventorySlots');
249
- inventorySlots.innerHTML='';
250
- for (let i=0;i<6;i++){
251
- const slot = document.createElement('div');
252
- slot.className='bg-gray-700 h-16 rounded-lg flex flex-col items-center justify-center text-xs';
253
- slot.dataset.index = i;
254
- updateSlot(slot, player.weapons[i] || null);
255
- inventorySlots.appendChild(slot);
256
- }
257
- updatePlayerStats();
258
- }
259
- function updateSlot(elem, weapon) {
260
- elem.innerHTML = weapon ? `<div style="font-size:10px">${weapon.name}</div><div style="color:${weapon.color}">●</div>` : '<i data-feather="box" class="text-gray-400"></i><span>Empty</span>';
261
- }
262
 
263
- // --- Player Stats UI update ---
264
- function updatePlayerStats() {
265
- document.getElementById('playerHealth').textContent = `${Math.max(0,Math.floor(player.health))}%`;
266
- document.getElementById('playerArmor').textContent = `${Math.floor(player.armor)}%`;
267
- document.getElementById('playerKills').textContent = player.kills;
268
- document.getElementById('playerMaterials').textContent = player.materials;
269
- document.getElementById('currentBiome').textContent = biomeAt(player.x, player.y);
270
- // refresh inventory slots visuals
271
- const slots = document.querySelectorAll('#inventorySlots > div');
272
- slots.forEach(s => updateSlot(s, player.weapons[parseInt(s.dataset.index)] || null));
273
- feather.replace();
274
  }
275
 
276
- // --- Damage & interaction ---
277
- function shootBullet(originX, originY, targetX, targetY, weapon) {
278
- const speed = 1000; // px per sec
279
- const angle = Math.atan2(targetY-originY, targetX-originX);
280
- bullets.push({
281
- x: originX, y: originY, vx: Math.cos(angle)*speed, vy: Math.sin(angle)*speed,
282
- dmg: weapon ? weapon.dmg : 8, color: weapon?weapon.color:'#fff', life: 1.5, traveled:0
283
- });
284
- }
285
 
286
- function applyDamageToObject(obj, dmg) {
287
- obj.hp -= dmg;
288
- if (obj.hp <= 0) {
289
- // drop materials
290
- const gained = obj.type==='wood'? randInt(3,10) : randInt(8,20);
291
- player.materials += gained;
292
- // fade out by setting hp to 0 and marking removed
293
- obj.dead = true;
294
- }
295
- }
296
 
297
- function interactNearby() {
298
- // Chests in range
299
- const range = 48;
300
- for (const chest of chests) {
301
- if (chest.opened) continue;
302
- const dx = chest.x - player.x, dy = chest.y - player.y;
303
- const dist = Math.hypot(dx,dy);
304
- if (dist < range) {
305
- chest.opened = true;
306
- chest.openProgress = 1;
307
- const loot = chest.loot;
308
- if (loot.type === 'medkit') {
309
- player.health = Math.min(100, player.health + loot.heal);
310
- } else if (loot.type === 'materials') {
311
- player.materials += loot.amount;
312
- } else if (loot.type === 'weapon') {
313
- // put into inventory first empty slot or replace current
314
- let placed=false;
315
- for (let i=0;i<6;i++){
316
- if (!player.weapons[i]) { player.weapons[i] = loot.weapon; player.currentWeaponIndex = i; placed=true; break; }
317
- }
318
- if (!placed) player.weapons[player.currentWeaponIndex] = loot.weapon;
319
- }
320
- updatePlayerStats();
321
- // small effect: spawn particles (simple)
322
- for (let i=0;i<8;i++) {
323
- bullets.push({ x:chest.x, y:chest.y, vx:(Math.random()-0.5)*200, vy:(Math.random()-0.5)*200, dmg:0, color:'#ffd86b', life:0.6, traveled:0, tracer:true });
324
- }
325
- return;
326
- }
327
- }
328
- // buildable objects: optionally harvest by 'E' if close and not shooting
329
- for (const obj of objects) {
330
- if (obj.dead) continue;
331
- const dx = obj.x - player.x, dy = obj.y - player.y;
332
- if (Math.hypot(dx,dy) < range) {
333
- // harvest yields small materials and destroys object
334
- const gain = obj.type==='wood'? 2 : 5;
335
- player.materials += gain;
336
- obj.dead = true;
337
- updatePlayerStats();
338
- return;
339
- }
340
- }
341
  }
342
 
343
- // --- Game loop ---
344
- let lastTime = 0;
345
- function gameLoop(ts) {
346
- if (!gameActive) return;
347
- if (!lastTime) lastTime = ts;
348
- const dt = Math.min(0.05, (ts - lastTime) / 1000); // clamp to avoid big jumps
349
- lastTime = ts;
350
-
351
- // Movement
352
- let dx=0, dy=0;
353
- if (keys.w) dy -= 1;
354
- if (keys.s) dy += 1;
355
- if (keys.a) dx -= 1;
356
- if (keys.d) dx += 1;
357
- if (dx!==0 || dy!==0) {
358
- const len = Math.hypot(dx,dy);
359
- player.x += (dx/len) * player.speed * dt;
360
- player.y += (dy/len) * player.speed * dt;
361
- }
362
- // confine to world
363
- player.x = Math.max(16, Math.min(WORLD.width-16, player.x));
364
- player.y = Math.max(16, Math.min(WORLD.height-16, player.y));
365
-
366
- // camera
367
- cameraUpdate();
368
-
369
- // shooting
370
- if (mouse.down) {
371
- const weapon = player.weapons[player.currentWeaponIndex] || { dmg:10, rate:300, color:'#fff' };
372
- if (ts - player.lastShot > (weapon.rate || 300)) {
373
- player.lastShot = ts;
374
- shootBullet(player.x + Math.cos(player.angle)*20, player.y + Math.sin(player.angle)*20, mouse.worldX, mouse.worldY, weapon);
375
- }
376
- }
377
 
378
- // bullets update & collision with objects and chests (simple)
379
- for (let i=bullets.length-1;i>=0;i--){
380
- const b = bullets[i];
381
- b.x += b.vx * dt;
382
- b.y += b.vy * dt;
383
- b.traveled += Math.hypot(b.vx*dt, b.vy*dt);
384
- b.life -= dt;
385
- // collision with breakables
386
- if (!b.tracer && b.dmg > 0) {
387
- for (const obj of objects) {
388
- if (obj.dead) continue;
389
- const d = Math.hypot(obj.x - b.x, obj.y - b.y);
390
- if (d < 20) {
391
- applyDamageToObject(obj, b.dmg);
392
- b.life = -1;
393
- break;
394
- }
395
- }
396
- // collision with chests
397
- for (const chest of chests) {
398
- if (chest.opened) continue;
399
- const d2 = Math.hypot(chest.x - b.x, chest.y - b.y);
400
- if (d2 < 20) { chest.opened = true; chest.openProgress = 1; b.life = -1; /* chest opened early */ }
401
- }
402
- }
403
- if (b.life <= 0 || b.traveled > 2000) bullets.splice(i,1);
404
- }
405
 
406
- // update objects removal
407
- for (let i=objects.length-1;i>=0;i--){
408
- if (objects[i].dead) objects.splice(i,1);
409
- }
 
 
 
 
 
410
 
411
- // draw
412
- ctx.clearRect(0,0,canvas.width,canvas.height);
413
- drawWorld();
414
- drawObjects();
415
- drawChests();
416
- drawBullets();
417
- drawPlayer();
418
- drawCrosshair();
419
 
420
- // storm handling and hud warnings
421
- updateStorm(dt);
422
- if (storm.active && playerInStorm()) stormWarningEl.classList.remove('hidden'); else stormWarningEl.classList.add('hidden');
 
 
 
 
423
 
424
- // interact on E
425
- if (keys.e) { interactNearby(); keys.e=false; }
426
 
427
- updatePlayerStats();
428
- requestAnimationFrame(gameLoop);
429
- }
430
 
431
- // --- Drawing helpers ---
432
- function worldToScreen(wx, wy) { return { x: wx - camera.x, y: wy - camera.y }; }
433
-
434
- function drawWorld() {
435
- // draw simple biome tiles
436
- const cols = Math.ceil(WORLD.width / TILE_SIZE);
437
- const rows = Math.ceil(WORLD.height / TILE_SIZE);
438
- for (let by=0; by<rows; by++) {
439
- for (let bx=0; bx<cols; bx++) {
440
- const x = bx * TILE_SIZE, y = by * TILE_SIZE;
441
- const b = biomeAt(x+1,y+1);
442
- let color = '#203a2b';
443
- if (b === 'desert') color = '#cbb78b';
444
- if (b === 'forest') color = '#1f4d2c';
445
- if (b === 'oasis') color = '#2a4f5a';
446
- if (b === 'ruins') color = '#4a3b3b';
447
-
448
- const scr = worldToScreen(x,y);
449
- ctx.fillStyle = color;
450
- ctx.fillRect(scr.x, scr.y, TILE_SIZE, TILE_SIZE);
451
- // subtle grid
452
- ctx.strokeStyle = 'rgba(0,0,0,0.15)';
453
- ctx.strokeRect(scr.x, scr.y, TILE_SIZE, TILE_SIZE);
454
  }
 
 
455
  }
456
-
457
- // draw storm (world space)
458
- ctx.save();
459
- const screenCenter = worldToScreen(storm.centerX, storm.centerY);
460
- const sr = storm.radius;
461
- const gradient = ctx.createRadialGradient(screenCenter.x, screenCenter.y, sr*0.2, screenCenter.x, screenCenter.y, sr);
462
- gradient.addColorStop(0, 'rgba(100,149,237,0.05)');
463
- gradient.addColorStop(1, 'rgba(100,149,237,0.35)');
464
- ctx.fillStyle = gradient;
465
- ctx.beginPath();
466
- ctx.arc(screenCenter.x, screenCenter.y, sr, 0, Math.PI*2);
467
- ctx.fill();
468
- ctx.restore();
469
  }
470
 
471
- function drawObjects() {
472
- for (const obj of objects) {
473
- const s = worldToScreen(obj.x, obj.y);
474
- ctx.save();
475
- // simple 3D-ish block (shadow + top)
476
- const h = obj.type === 'wood' ? 18 : (obj.type==='stone'? 12:24);
477
- // shadow
478
- ctx.fillStyle = 'rgba(0,0,0,0.18)';
479
- ctx.beginPath();
480
- ctx.ellipse(s.x, s.y + 8, 18, 8, 0, 0, Math.PI*2);
481
- ctx.fill();
482
- // body
483
- if (obj.type === 'wood') ctx.fillStyle = '#6b3b1a';
484
- if (obj.type === 'stone') ctx.fillStyle = '#6b6b6b';
485
- if (obj.type === 'wall') ctx.fillStyle = '#8b5a32';
486
- ctx.fillRect(s.x-12, s.y-h, 24, h);
487
- // health bar
488
- ctx.fillStyle = 'black';
489
- ctx.fillRect(s.x-14, s.y-h-8, 28, 5);
490
- ctx.fillStyle = 'lime';
491
- const hpPct = Math.max(0, obj.hp / obj.maxHp);
492
- ctx.fillRect(s.x-14, s.y-h-8, 28*hpPct, 5);
493
- ctx.restore();
494
  }
495
- }
496
-
497
- function drawChests() {
498
- for (const chest of chests) {
499
- const s = worldToScreen(chest.x, chest.y);
500
- ctx.save();
501
- // shadow
502
- ctx.fillStyle = 'rgba(0,0,0,0.25)';
503
- ctx.beginPath();
504
- ctx.ellipse(s.x, s.y+10, 22, 10, 0, 0, Math.PI*2);
505
- ctx.fill();
506
-
507
- // base
508
- ctx.fillStyle = chest.opened ? '#7b3e00' : '#a56b2a';
509
- ctx.fillRect(s.x-18, s.y-12, 36, 20);
510
-
511
- // lid (open animation)
512
- ctx.translate(s.x, s.y-12);
513
- const openAngle = chest.opened ? Math.PI*0.45 : 0;
514
- ctx.save();
515
- ctx.rotate(-openAngle);
516
- ctx.fillStyle = chest.opened ? '#ffd86b' : '#caa15e';
517
- ctx.fillRect(-18, -12-4, 36, 8);
518
- ctx.restore();
519
- ctx.restore();
520
  }
 
521
  }
522
 
523
- function drawBullets() {
524
- for (const b of bullets) {
525
- const s = worldToScreen(b.x, b.y);
526
- ctx.save();
527
- if (b.tracer) {
528
- ctx.fillStyle = b.color;
529
- ctx.globalAlpha = 0.9;
530
- ctx.beginPath();
531
- ctx.arc(s.x, s.y, 3, 0, Math.PI*2);
532
- ctx.fill();
533
- } else {
534
- // animated streak
535
- const backX = s.x - (b.vx * 0.02);
536
- const backY = s.y - (b.vy * 0.02);
537
- const grad = ctx.createLinearGradient(backX, backY, s.x, s.y);
538
- grad.addColorStop(0, 'rgba(255,255,255,0)');
539
- grad.addColorStop(0.7, b.color);
540
- grad.addColorStop(1, '#fff');
541
- ctx.strokeStyle = grad;
542
- ctx.lineWidth = 3;
543
- ctx.beginPath();
544
- ctx.moveTo(backX, backY);
545
- ctx.lineTo(s.x, s.y);
546
- ctx.stroke();
547
- // tip
548
- ctx.fillStyle = '#fff';
549
- ctx.beginPath();
550
- ctx.arc(s.x, s.y, 2.5, 0, Math.PI*2);
551
- ctx.fill();
552
- }
553
- ctx.restore();
554
- }
555
- }
556
 
557
- function drawPlayer() {
558
- const s = worldToScreen(player.x, player.y);
559
- ctx.save();
560
- ctx.translate(s.x, s.y);
561
- ctx.rotate(player.angle);
562
- // shadow
563
- ctx.fillStyle = 'rgba(0,0,0,0.25)';
564
- ctx.beginPath(); ctx.ellipse(0, 14, 18, 8, 0,0,Math.PI*2); ctx.fill();
565
- // body - triangle
566
- ctx.fillStyle = 'yellow';
567
- ctx.beginPath();
568
- ctx.moveTo(16,0); ctx.lineTo(-12,-10); ctx.lineTo(-12,10); ctx.closePath();
569
- ctx.fill();
570
- // small 3D 'height' effect
571
- ctx.fillStyle = '#e6c04a';
572
- ctx.fillRect(-6,6,4,6);
573
- ctx.restore();
574
  }
575
 
 
576
  function drawCrosshair() {
577
- const screenX = mouse.canvasX, screenY = mouse.canvasY;
578
  ctx.save();
579
  ctx.strokeStyle = 'yellow';
580
  ctx.lineWidth = 2;
581
  ctx.beginPath();
582
- ctx.moveTo(screenX-8, screenY); ctx.lineTo(screenX+8, screenY);
583
- ctx.moveTo(screenX, screenY-8); ctx.lineTo(screenX, screenY+8);
 
 
 
584
  ctx.stroke();
585
  ctx.restore();
586
  }
587
 
588
- // --- Storm mechanics ---
589
- function playerInStorm() {
590
- const dx = player.x - storm.centerX, dy = player.y - storm.centerY;
591
- return Math.hypot(dx,dy) > storm.radius;
592
- }
593
- let stormDamageAccumulator = 0;
594
- function updateStorm(dt) {
595
- if (!storm.active) return;
596
- storm.radius -= storm.closingSpeed * dt * 50; // multiplier to make it noticeable
597
- if (storm.radius < 80) storm.radius = 80;
598
- if (playerInStorm()) {
599
- const progress = 1 - storm.radius / storm.maxRadius;
600
- const rate = storm.damagePerSecond * (1 + progress*4);
601
- stormDamageAccumulator += rate * dt;
602
- while (stormDamageAccumulator >= 1) {
603
- stormDamageAccumulator -= 1;
604
- player.health -= 1;
605
- if (player.health <= 0) { player.health = 0; playerDeath(); }
606
- }
607
- } else {
608
- stormDamageAccumulator = 0;
609
- }
610
- }
611
-
612
- // --- Game start / end ---
613
  function startGame() {
614
  gameActive = true;
 
615
  landingScreenEl.classList.add('hidden');
616
  gameScreenEl.classList.remove('hidden');
617
- selectedBiome && console.log('Start biome:', selectedBiome);
618
-
619
- // reset player & world
620
- player.x = WORLD.width/2 + (Math.random()-0.5)*200;
621
- player.y = WORLD.height/2 + (Math.random()-0.5)*200;
622
- player.health = 100; player.armor = 0; player.kills = 0; player.materials = 0; player.weapons = [];
623
- // give starter weapon
624
- player.weapons[0] = { name:'Pistol', dmg:12, rate:320, color:'#ffd86b' };
625
- player.currentWeaponIndex=0;
626
- populateWorld();
627
- initInventory();
628
- cameraUpdate();
629
 
630
- // timer & storm schedule
631
- gameTime = 300;
632
- storm.active = false;
 
 
 
 
633
  storm.radius = storm.maxRadius;
 
 
 
 
634
  document.getElementById('gameTimer').textContent = '5:00';
 
635
  timerInterval && clearInterval(timerInterval);
636
  timerInterval = setInterval(() => {
637
- if (!gameActive) { clearInterval(timerInterval); return; }
 
 
 
638
  gameTime--;
639
- const m = Math.floor(gameTime/60), s = gameTime%60;
640
- document.getElementById('gameTimer').textContent = `${m}:${s<10?'0'+s:s}`;
641
- // trigger storm at 4 minutes left
642
- if (gameTime === 240) { storm.active = true; stormWarningEl.classList.remove('hidden'); setTimeout(()=>stormWarningEl.classList.add('hidden'),4000); }
643
- if (gameTime <= 0) { clearInterval(timerInterval); endGame(); }
 
 
 
 
 
 
 
 
 
 
 
644
  }, 1000);
645
 
646
- lastTime = performance.now();
647
  requestAnimationFrame(gameLoop);
648
  }
649
 
 
650
  function endGame() {
651
  gameActive = false;
652
- alert('Match over!');
653
  }
654
 
 
655
  function playerDeath() {
656
  gameActive = false;
657
  deathScreenEl.classList.remove('hidden');
658
  }
659
 
 
660
  document.getElementById('respawnBtn').addEventListener('click', () => {
661
  deathScreenEl.classList.add('hidden');
662
  landingScreenEl.classList.remove('hidden');
663
  });
664
 
665
- // Biome selectors
666
- document.querySelectorAll('.biome-selector').forEach(el => {
667
- el.addEventListener('click', () => {
668
- selectedBiome = el.getAttribute('data-biome');
669
  startGame();
670
  });
671
  });
672
 
673
- // Interaction key E
674
- window.addEventListener('keydown', (e) => {
675
- if (e.key.toLowerCase()==='e' && gameActive) keys.e = true;
676
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
677
 
678
- // Initialize
 
 
 
 
679
  resizeCanvas();
680
- populateWorld();
681
  initInventory();
682
  updatePlayerStats();
683
- feather.replace();
684
  </script>
685
  </body>
686
  </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" />
6
+ <title>BattleZone Royale</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <script src="https://unpkg.com/feather-icons"></script>
9
  <style>
10
+ html,
11
+ body,
12
+ #root {
13
+ height: 100%;
14
+ margin: 0;
15
+ background-color: #111827; /* Tailwind gray-900 */
16
+ color: white;
17
+ font-family: monospace;
18
+ }
19
+ #gameCanvas {
20
+ background-image: url('http://static.photos/nature/1200x630/42');
21
+ background-size: cover;
22
+ background-position: center;
23
+ display: block;
24
+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
25
+ user-select: none;
26
+ cursor: crosshair;
27
+ }
28
+ .biome-selector {
29
+ transition: all 0.3s ease;
30
+ }
31
+ .biome-selector:hover {
32
+ transform: scale(1.05);
33
+ box-shadow: 0 0 15px rgba(255, 215, 0, 0.5);
34
+ }
35
+ .storm-animation {
36
+ animation: stormPulse 3s infinite;
37
+ }
38
+ @keyframes stormPulse {
39
+ 0% {
40
+ box-shadow: 0 0 10px rgba(100, 149, 237, 0.5);
41
+ }
42
+ 50% {
43
+ box-shadow: 0 0 30px rgba(100, 149, 237, 0.8);
44
+ }
45
+ 100% {
46
+ box-shadow: 0 0 10px rgba(100, 149, 237, 0.5);
47
+ }
48
+ }
49
+ #stormWarning {
50
+ z-index: 10;
51
+ }
52
+ #deathScreen {
53
+ z-index: 20;
54
+ }
55
+ /* Fullscreen canvas container */
56
+ #canvasContainer {
57
+ position: relative;
58
+ flex: 1;
59
+ display: flex;
60
+ justify-content: center;
61
+ align-items: center;
62
+ height: 100vh;
63
+ overflow: hidden;
64
+ }
65
  </style>
66
  </head>
67
  <body>
68
+ <div id="landingScreen" class="flex flex-col items-center justify-center h-screen bg-gray-900 p-6">
69
+ <h1 class="text-5xl font-bold text-yellow-400 mb-8 flex items-center">
70
  <i data-feather="target" class="mr-2"></i> BattleZone Royale
71
  </h1>
72
+ <h2 class="text-3xl font-semibold text-yellow-400 mb-6 flex items-center">
73
+ <i data-feather="map-pin" class="mr-2"></i> Choose Landing Zone
74
+ </h2>
75
  <div class="grid grid-cols-2 gap-6 max-w-xl w-full">
76
+ <div
77
+ class="biome-selector bg-gray-700 p-6 rounded-lg cursor-pointer hover:bg-yellow-800 flex flex-col items-center"
78
+ data-biome="desert"
79
+ >
80
  <i data-feather="sun" class="text-yellow-300 mb-2"></i>
81
  <h3 class="font-bold text-lg">Golden Dunes</h3>
82
  <p class="text-xs">High loot, high risk</p>
83
  </div>
84
+ <div
85
+ class="biome-selector bg-gray-700 p-6 rounded-lg cursor-pointer hover:bg-green-800 flex flex-col items-center"
86
+ data-biome="forest"
87
+ >
88
  <i data-feather="tree" class="text-green-300 mb-2"></i>
89
  <h3 class="font-bold text-lg">Shadow Forest</h3>
90
  <p class="text-xs">Good cover, medium loot</p>
91
  </div>
92
+ <div
93
+ class="biome-selector bg-gray-700 p-6 rounded-lg cursor-pointer hover:bg-blue-800 flex flex-col items-center"
94
+ data-biome="oasis"
95
+ >
96
  <i data-feather="droplet" class="text-blue-300 mb-2"></i>
97
  <h3 class="font-bold text-lg">Crystal Oasis</h3>
98
  <p class="text-xs">Medium loot, low risk</p>
99
  </div>
100
+ <div
101
+ class="biome-selector bg-gray-700 p-6 rounded-lg cursor-pointer hover:bg-purple-800 flex flex-col items-center"
102
+ data-biome="ruins"
103
+ >
104
  <i data-feather="layers" class="text-purple-300 mb-2"></i>
105
  <h3 class="font-bold text-lg">Ancient Ruins</h3>
106
  <p class="text-xs">Legendary loot, dangerous</p>
 
109
  </div>
110
 
111
  <div id="gameScreen" class="hidden h-screen flex flex-col">
112
+ <!-- Game Header -->
113
+ <header
114
+ class="flex justify-between items-center border-b border-yellow-500 px-6 py-4"
115
+ style="flex-shrink: 0;"
116
+ >
117
+ <h1 class="text-4xl font-bold text-yellow-400 flex items-center">
118
+ <i data-feather="target" class="mr-2"></i> BattleZone Royale
119
+ </h1>
120
+ <div class="flex space-x-4 text-white">
121
+ <div class="bg-gray-800 px-4 py-2 rounded-lg flex items-center">
122
+ <i data-feather="users" class="mr-2"></i>
123
+ <span id="playerCount">1/100</span>
124
+ </div>
125
+ <div class="bg-gray-800 px-4 py-2 rounded-lg flex items-center">
126
+ <i data-feather="clock" class="mr-2"></i>
127
+ <span id="gameTimer">5:00</span>
128
+ </div>
129
  </div>
130
  </header>
131
 
132
  <div class="flex flex-1 overflow-hidden">
133
+ <!-- Main Game Canvas -->
134
+ <div
135
+ id="canvasContainer"
136
+ class="relative bg-gray-900 border-4 border-gray-700 rounded-xl max-w-full max-h-full"
137
+ >
138
  <canvas id="gameCanvas"></canvas>
139
 
140
+ <!-- Storm Warning -->
141
+ <div
142
+ id="stormWarning"
143
+ class="hidden absolute top-6 left-1/2 transform -translate-x-1/2 bg-red-900 bg-opacity-80 text-white px-6 py-3 rounded-lg flex items-center storm-animation"
144
+ >
145
  <i data-feather="alert-circle" class="mr-2"></i>
146
  <span>STORM APPROACHING! MOVE TO SAFE ZONE!</span>
147
  </div>
148
 
149
+ <!-- Death Screen -->
150
+ <div
151
+ id="deathScreen"
152
+ class="hidden absolute inset-0 bg-black bg-opacity-80 flex flex-col items-center justify-center rounded-xl"
153
+ >
154
  <i data-feather="skull" class="w-16 h-16 text-red-500 mb-4"></i>
155
  <h2 class="text-4xl font-bold text-red-500 mb-4">YOU DIED!</h2>
156
  <p class="text-xl mb-6">Better luck next time, soldier!</p>
157
+ <button
158
+ id="respawnBtn"
159
+ class="bg-yellow-500 hover:bg-yellow-600 text-black font-bold py-3 px-6 rounded-lg flex items-center"
160
+ >
161
  <i data-feather="refresh-cw" class="mr-2"></i> Respawn
162
  </button>
163
  </div>
164
  </div>
165
 
166
+ <!-- Game Controls and Info -->
167
+ <aside
168
+ class="w-80 bg-gray-800 rounded-xl p-6 overflow-y-auto flex-shrink-0 flex flex-col gap-8"
169
+ style="max-height: 100vh;"
170
+ >
171
+ <!-- Player Stats -->
172
  <section>
173
+ <h2
174
+ class="text-2xl font-bold text-yellow-400 mb-4 flex items-center"
175
+ >
176
+ <i data-feather="activity" class="mr-2"></i> Player Stats
177
+ </h2>
178
  <div class="space-y-3">
179
+ <div class="flex justify-between">
180
+ <span>Health:</span>
181
+ <span id="playerHealth" class="font-bold">100%</span>
182
+ </div>
183
+ <div class="flex justify-between">
184
+ <span>Armor:</span>
185
+ <span id="playerArmor" class="font-bold">0%</span>
186
+ </div>
187
+ <div class="flex justify-between">
188
+ <span>Kills:</span>
189
+ <span id="playerKills" class="font-bold text-red-400">0</span>
190
+ </div>
191
  </div>
192
  </section>
193
 
194
+ <!-- Inventory -->
195
  <section>
196
+ <h2
197
+ class="text-2xl font-bold text-yellow-400 mb-4 flex items-center"
198
+ >
199
+ <i data-feather="briefcase" class="mr-2"></i> Inventory
200
+ </h2>
201
+ <div
202
+ id="inventorySlots"
203
+ class="grid grid-cols-3 gap-2"
204
+ aria-label="Inventory slots"
205
+ ></div>
206
  </section>
207
 
208
+ <!-- Controls Info -->
209
  <section>
210
+ <h2
211
+ class="text-2xl font-bold text-yellow-400 mb-4 flex items-center"
212
+ >
213
+ <i data-feather="command" class="mr-2"></i> Controls
214
+ </h2>
215
  <div class="space-y-2 text-sm">
216
+ <div class="flex items-center">
217
+ <span
218
+ class="bg-gray-700 px-2 py-1 rounded mr-2"
219
+ >WASD</span
220
+ >
221
+ <span>Move character</span>
222
+ </div>
223
+ <div class="flex items-center">
224
+ <span
225
+ class="bg-gray-700 px-2 py-1 rounded mr-2"
226
+ >Mouse</span
227
+ >
228
+ <span>Aim and shoot</span>
229
+ </div>
230
+ <div class="flex items-center">
231
+ <span
232
+ class="bg-gray-700 px-2 py-1 rounded mr-2"
233
+ >E</span
234
+ >
235
+ <span>Loot chests/open doors</span>
236
+ </div>
237
+ <div class="flex items-center">
238
+ <span
239
+ class="bg-gray-700 px-2 py-1 rounded mr-2"
240
+ >1-5</span
241
+ >
242
+ <span>Switch weapons</span>
243
+ </div>
244
  </div>
245
  </section>
246
  </aside>
 
248
  </div>
249
 
250
  <script>
251
+ // Game variables
252
  let gameActive = false;
253
+ let playerHealth = 100;
254
+ let playerArmor = 0;
255
+ let playerKills = 0;
256
+ let gameTime = 300; // 5 minutes in seconds
257
+ let stormActive = false;
258
+ let selectedBiome = null;
259
+
260
+ // Canvas and context
261
  const canvas = document.getElementById('gameCanvas');
262
  const ctx = canvas.getContext('2d');
 
 
263
  const stormWarningEl = document.getElementById('stormWarning');
264
  const deathScreenEl = document.getElementById('deathScreen');
265
+ const landingScreenEl = document.getElementById('landingScreen');
266
+ const gameScreenEl = document.getElementById('gameScreen');
267
 
268
+ // Player and storm state
269
+ const player = {
270
+ x: 0,
271
+ y: 0,
272
+ radius: 15,
273
+ angle: 0,
274
+ speed: 3,
275
+ health: 100,
276
+ armor: 0,
277
+ kills: 0,
278
+ };
279
+
280
+ const storm = {
281
+ maxRadius: 600,
282
+ radius: 600,
283
+ centerX: 0,
284
+ centerY: 0,
285
+ damagePerSecond: 1,
286
+ closingSpeed: 0.5, // radius shrink per second
287
+ };
288
+
289
+ // Movement input
290
+ const keys = {
291
+ w: false,
292
+ a: false,
293
+ s: false,
294
+ d: false,
295
+ };
296
+
297
+ // Mouse
298
+ const mouse = {
299
+ x: 0,
300
+ y: 0,
301
+ canvasX: 0,
302
+ canvasY: 0,
303
+ };
304
+
305
+ // Resize canvas to fill container
306
  function resizeCanvas() {
307
  const container = document.getElementById('canvasContainer');
308
  canvas.width = container.clientWidth;
309
  canvas.height = container.clientHeight;
310
+ // Reset storm center to canvas center on resize
311
+ storm.centerX = canvas.width / 2;
312
+ storm.centerY = canvas.height / 2;
313
  }
314
  window.addEventListener('resize', resizeCanvas);
315
 
316
+ // Initialize player position center of canvas on start
317
+ function initPlayerPosition() {
318
+ player.x = canvas.width / 2;
319
+ player.y = canvas.height / 2;
 
 
 
 
 
320
  }
321
 
322
+ // Update player stats UI
323
+ function updatePlayerStats() {
324
+ document.getElementById('playerHealth').textContent = `${playerHealth}%`;
325
+ document.getElementById('playerArmor').textContent = `${playerArmor}%`;
326
+ document.getElementById('playerKills').textContent = playerKills;
327
+ }
328
 
329
+ // Initialize inventory slots
330
+ function initInventory() {
331
+ const inventorySlots = document.getElementById('inventorySlots');
332
+ inventorySlots.innerHTML = '';
333
+
334
+ for (let i = 0; i < 6; i++) {
335
+ const slot = document.createElement('div');
336
+ slot.className =
337
+ 'bg-gray-700 h-16 rounded-lg flex flex-col items-center justify-center text-xs';
338
+ slot.innerHTML = '<i data-feather="box" class="text-gray-400"></i><span>Empty</span>';
339
+ inventorySlots.appendChild(slot);
340
+ }
341
+ }
342
 
343
+ // Handle keyboard input for movement
344
  window.addEventListener('keydown', (e) => {
345
  if (!gameActive) return;
346
+ const key = e.key.toLowerCase();
347
+ if (keys.hasOwnProperty(key)) {
348
+ keys[key] = true;
 
 
349
  }
350
  });
351
  window.addEventListener('keyup', (e) => {
352
  if (!gameActive) return;
353
+ const key = e.key.toLowerCase();
354
+ if (keys.hasOwnProperty(key)) {
355
+ keys[key] = false;
 
356
  }
357
  });
358
 
359
+ // Handle mouse movement for aiming
360
  canvas.addEventListener('mousemove', (e) => {
361
  const rect = canvas.getBoundingClientRect();
362
  mouse.canvasX = e.clientX - rect.left;
363
  mouse.canvasY = e.clientY - rect.top;
364
+
365
+ // Calculate angle from player to mouse position
366
+ const dx = mouse.canvasX - player.x;
367
+ const dy = mouse.canvasY - player.y;
368
  player.angle = Math.atan2(dy, dx);
369
  });
370
 
371
+ // Move player based on keys pressed, constrained within canvas
372
+ function movePlayer() {
373
+ if (!gameActive) return;
374
+ let dx = 0,
375
+ dy = 0;
376
+ if (keys.w) dy -= player.speed;
377
+ if (keys.s) dy += player.speed;
378
+ if (keys.a) dx -= player.speed;
379
+ if (keys.d) dx += player.speed;
380
+
381
+ // Normalize diagonal movement
382
+ if (dx !== 0 && dy !== 0) {
383
+ dx *= Math.SQRT1_2;
384
+ dy *= Math.SQRT1_2;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
 
387
+ player.x += dx;
388
+ player.y += dy;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
 
390
+ // Confine player inside canvas
391
+ player.x = Math.min(Math.max(player.radius, player.x), canvas.width - player.radius);
392
+ player.y = Math.min(Math.max(player.radius, player.y), canvas.height - player.radius);
 
 
 
 
 
 
 
 
393
  }
394
 
395
+ // Draw player as a triangle pointing to aim direction
396
+ function drawPlayer() {
397
+ ctx.save();
398
+ ctx.translate(player.x, player.y);
399
+ ctx.rotate(player.angle);
 
 
 
 
400
 
401
+ // Player body (triangle)
402
+ ctx.fillStyle = 'yellow';
403
+ ctx.beginPath();
404
+ ctx.moveTo(20, 0);
405
+ ctx.lineTo(-15, -12);
406
+ ctx.lineTo(-15, 12);
407
+ ctx.closePath();
408
+ ctx.fill();
 
 
409
 
410
+ ctx.restore();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
  }
412
 
413
+ // Draw the shrinking storm circle with increasing damage indication
414
+ function drawStorm() {
415
+ ctx.save();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
 
417
+ // Storm fill with transparent blue
418
+ const gradient = ctx.createRadialGradient(
419
+ storm.centerX,
420
+ storm.centerY,
421
+ storm.radius * 0.6,
422
+ storm.centerX,
423
+ storm.centerY,
424
+ storm.radius
425
+ );
426
+ gradient.addColorStop(0, 'rgba(100, 149, 237, 0.1)');
427
+ gradient.addColorStop(1, 'rgba(100, 149, 237, 0.6)');
428
+ ctx.fillStyle = gradient;
429
+ ctx.beginPath();
430
+ ctx.arc(storm.centerX, storm.centerY, storm.radius, 0, Math.PI * 2);
431
+ ctx.fill();
 
 
 
 
 
 
 
 
 
 
 
 
432
 
433
+ // Storm outline thickening and color shifting as radius shrinks
434
+ const stormProgress = 1 - storm.radius / storm.maxRadius; // 0 to 1
435
+ const outlineWidth = 10 + stormProgress * 20;
436
+ const redIntensity = Math.min(255, Math.floor(100 + stormProgress * 155));
437
+ ctx.strokeStyle = `rgba(${redIntensity}, 50, 150, 0.8)`;
438
+ ctx.lineWidth = outlineWidth;
439
+ ctx.beginPath();
440
+ ctx.arc(storm.centerX, storm.centerY, storm.radius, 0, Math.PI * 2);
441
+ ctx.stroke();
442
 
443
+ ctx.restore();
444
+ }
 
 
 
 
 
 
445
 
446
+ // Check if player is outside storm radius
447
+ function playerInStorm() {
448
+ const dx = player.x - storm.centerX;
449
+ const dy = player.y - storm.centerY;
450
+ const dist = Math.sqrt(dx * dx + dy * dy);
451
+ return dist > storm.radius;
452
+ }
453
 
454
+ // Storm damage application
455
+ let stormDamageAccumulator = 0;
456
 
457
+ function applyStormDamage(deltaTime) {
458
+ if (!stormActive) return;
 
459
 
460
+ if (playerInStorm()) {
461
+ // Damage increases as storm closes
462
+ const stormProgress = 1 - storm.radius / storm.maxRadius; // 0 to 1
463
+ const damageRate = storm.damagePerSecond * (1 + stormProgress * 4); // up to 5x damage
464
+ stormDamageAccumulator += damageRate * deltaTime;
465
+ while (stormDamageAccumulator >= 1) {
466
+ stormDamageAccumulator -= 1;
467
+ applyDamage(1);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
  }
469
+ } else {
470
+ stormDamageAccumulator = 0;
471
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
472
  }
473
 
474
+ // Apply damage to player health considering armor
475
+ function applyDamage(amount) {
476
+ if (playerArmor > 0) {
477
+ const armorAbsorb = Math.min(playerArmor, amount * 0.7); // Armor absorbs 70% of damage
478
+ playerArmor -= armorAbsorb;
479
+ amount -= armorAbsorb;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
480
  }
481
+ playerHealth -= amount;
482
+ if (playerHealth <= 0) {
483
+ playerHealth = 0;
484
+ playerDeath();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
485
  }
486
+ updatePlayerStats();
487
  }
488
 
489
+ // Storm radius shrinking over time
490
+ function updateStorm(deltaTime) {
491
+ if (!stormActive) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
492
 
493
+ storm.radius -= storm.closingSpeed * deltaTime;
494
+ if (storm.radius < 50) storm.radius = 50;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
495
  }
496
 
497
+ // Draw aim crosshair at mouse position
498
  function drawCrosshair() {
 
499
  ctx.save();
500
  ctx.strokeStyle = 'yellow';
501
  ctx.lineWidth = 2;
502
  ctx.beginPath();
503
+ const size = 10;
504
+ ctx.moveTo(mouse.canvasX - size, mouse.canvasY);
505
+ ctx.lineTo(mouse.canvasX + size, mouse.canvasY);
506
+ ctx.moveTo(mouse.canvasX, mouse.canvasY - size);
507
+ ctx.lineTo(mouse.canvasX, mouse.canvasY + size);
508
  ctx.stroke();
509
  ctx.restore();
510
  }
511
 
512
+ // Game timer and storm activation
513
+ let timerInterval;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
514
  function startGame() {
515
  gameActive = true;
516
+ selectedBiome && console.log('Selected biome:', selectedBiome);
517
  landingScreenEl.classList.add('hidden');
518
  gameScreenEl.classList.remove('hidden');
519
+ stormWarningEl.classList.add('hidden');
 
 
 
 
 
 
 
 
 
 
 
520
 
521
+ // Reset stats
522
+ playerHealth = 100;
523
+ playerArmor = 0;
524
+ playerKills = 0;
525
+ updatePlayerStats();
526
+ initInventory();
527
+ initPlayerPosition();
528
  storm.radius = storm.maxRadius;
529
+ stormActive = false;
530
+ stormWarningEl.classList.add('hidden');
531
+
532
+ gameTime = 300; // 5 minutes
533
  document.getElementById('gameTimer').textContent = '5:00';
534
+
535
  timerInterval && clearInterval(timerInterval);
536
  timerInterval = setInterval(() => {
537
+ if (!gameActive) {
538
+ clearInterval(timerInterval);
539
+ return;
540
+ }
541
  gameTime--;
542
+ const minutes = Math.floor(gameTime / 60);
543
+ const seconds = gameTime % 60;
544
+ document.getElementById('gameTimer').textContent = `${minutes}:${seconds < 10 ? '0' + seconds : seconds}`;
545
+
546
+ // Start storm after 60 seconds (4 minutes left)
547
+ if (gameTime === 240 && !stormActive) {
548
+ stormActive = true;
549
+ stormWarningEl.classList.remove('hidden');
550
+ setTimeout(() => stormWarningEl.classList.add('hidden'), 5000);
551
+ }
552
+
553
+ // End game when timer hits 0
554
+ if (gameTime <= 0) {
555
+ clearInterval(timerInterval);
556
+ endGame();
557
+ }
558
  }, 1000);
559
 
560
+ lastFrameTime = performance.now();
561
  requestAnimationFrame(gameLoop);
562
  }
563
 
564
+ // End game
565
  function endGame() {
566
  gameActive = false;
567
+ alert('Congratulations! You survived the BattleZone!');
568
  }
569
 
570
+ // Player death
571
  function playerDeath() {
572
  gameActive = false;
573
  deathScreenEl.classList.remove('hidden');
574
  }
575
 
576
+ // Respawn button handler
577
  document.getElementById('respawnBtn').addEventListener('click', () => {
578
  deathScreenEl.classList.add('hidden');
579
  landingScreenEl.classList.remove('hidden');
580
  });
581
 
582
+ // Biome selection handler
583
+ document.querySelectorAll('.biome-selector').forEach((selector) => {
584
+ selector.addEventListener('click', () => {
585
+ selectedBiome = selector.getAttribute('data-biome');
586
  startGame();
587
  });
588
  });
589
 
590
+ // Game loop with delta time for smooth movement and storm update
591
+ let lastFrameTime = 0;
592
+ function gameLoop(timestamp) {
593
+ if (!gameActive) return;
594
+ if (!lastFrameTime) lastFrameTime = timestamp;
595
+ const deltaTime = (timestamp - lastFrameTime) / 1000; // seconds
596
+ lastFrameTime = timestamp;
597
+
598
+ // Update
599
+ movePlayer();
600
+ updateStorm(deltaTime);
601
+ applyStormDamage(deltaTime);
602
+
603
+ // Clear
604
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
605
+
606
+ // Draw
607
+ drawStorm();
608
+ drawPlayer();
609
+ drawCrosshair();
610
+
611
+ // Show storm warning if player is outside storm radius while storm active
612
+ if (stormActive && playerInStorm()) {
613
+ stormWarningEl.classList.remove('hidden');
614
+ } else {
615
+ stormWarningEl.classList.add('hidden');
616
+ }
617
 
618
+ requestAnimationFrame(gameLoop);
619
+ }
620
+
621
+ // Initialize feather icons and setup
622
+ feather.replace();
623
  resizeCanvas();
 
624
  initInventory();
625
  updatePlayerStats();
 
626
  </script>
627
  </body>
628
  </html>