bbc123321 commited on
Commit
9029f27
·
verified ·
1 Parent(s): 0c2386c

Manual changes saved

Browse files
Files changed (1) hide show
  1. index.html +214 -277
index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="utf-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <title>BattleZone Royale - Mobile Controls & Medkit Hold</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <script src="https://unpkg.com/feather-icons"></script>
9
  <style>
@@ -26,47 +26,98 @@
26
  .biome-selected { outline: 3px solid rgba(96,165,250,0.9); }
27
 
28
  /* Loading screen */
29
- #loadingScreen { position: absolute; inset: 0; z-index: 80; display: none; align-items: center; justify-content: center; overflow: hidden; color: #fff; padding: 24px; box-sizing: border-box; }
30
-
31
- /* Mobile controls */
32
- #mobileControls {
33
  position: absolute;
34
- bottom: 14px;
35
- left: 14px;
36
- right: 14px;
37
- pointer-events: auto;
38
- z-index: 60;
39
  display: none;
40
- touch-action: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  }
42
- .mobile-left, .mobile-right { display:flex; gap:10px; align-items:center; }
43
- .dpad { width: 140px; height: 140px; position: relative; }
44
- .dpad button {
45
- position: absolute; width: 46px; height: 46px; border-radius: 10px; background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.04); color:#fff; font-weight:700;
46
- display:flex; align-items:center; justify-content:center; -webkit-user-select:none; user-select:none;
47
  }
48
- .dpad button:active { background: rgba(255,255,255,0.10); }
49
- .dpad .up { left: 50%; transform: translateX(-50%); top: 6px; }
50
- .dpad .down { left: 50%; transform: translateX(-50%); bottom: 6px; }
51
- .dpad .left { left: 6px; top: 50%; transform: translateY(-50%); }
52
- .dpad .right { right: 6px; top: 50%; transform: translateY(-50%); }
53
-
54
- .mobile-actions { position:absolute; right:14px; bottom:14px; display:flex; flex-direction:column; gap:10px; align-items:flex-end; z-index:61; }
55
- .action-btn { width:64px; height:64px; border-radius:12px; background: rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.04); display:flex; align-items:center; justify-content:center; color:#fff; font-weight:700; }
56
- .action-btn:active { background: rgba(255,255,255,0.10); }
57
- .small-actions { display:flex; gap:8px; margin-top:8px; }
58
-
59
- /* progress ring for medkit button */
60
- .medkit-wrap { position:relative; width:64px; height:64px; display:flex; align-items:center; justify-content:center; }
61
- .medkit-progress {
62
- position:absolute; top:0; left:0; width:100%; height:100%; pointer-events:none;
63
- opacity:0.95;
64
  }
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
- /* Loading card styles (from previous) */
67
- .loading-bg { position: absolute; inset: -20%; background: linear-gradient(120deg, #0b1220 0%, #123049 30%, #5b2a6a 60%, #2f4b2f 100%); filter: blur(20px) saturate(1.1); animation: bgShift 18s linear infinite; transform: scale(1.1); opacity: 0.85; }
68
- @keyframes bgShift { 0% { filter: hue-rotate(0deg) blur(20px); transform: scale(1.06) rotate(0.01deg); } 25% { filter: hue-rotate(45deg) blur(18px); transform: scale(1.08) rotate(0.02deg); } 50% { filter: hue-rotate(90deg) blur(22px); transform: scale(1.04) rotate(-0.01deg); } 75% { filter: hue-rotate(180deg) blur(20px); transform: scale(1.07) rotate(0.01deg); } 100% { filter: hue-rotate(360deg) blur(20px); transform: scale(1.06) rotate(-0.02deg); } }
69
- .loading-card { position: relative; z-index: 2; max-width: 880px; width: calc(100% - 48px); background: rgba(6,10,18,0.55); border: 1px solid rgba(255,255,255,0.06); box-shadow: 0 12px 40px rgba(0,0,0,0.7); border-radius: 12px; padding: 20px; display:flex; gap:18px; align-items:center; }
 
 
 
 
70
  </style>
71
  </head>
72
  <body>
@@ -106,7 +157,7 @@
106
  <li><strong>WASD</strong> — Move</li>
107
  <li><strong>Mouse</strong> — Aim</li>
108
  <li><strong>Left Click</strong> — Shoot (if weapon equipped or in selected slot) or pickaxe melee</li>
109
- <li><strong>E</strong> — Hold to use medkit (3s) OR tap to Interact / Loot</li>
110
  <li><strong>Q</strong> — Build (costs 10 materials)</li>
111
  <li><strong>R</strong> — Reload selected weapon</li>
112
  <li><strong>1-5</strong> — Select inventory slot (also equips it)</li>
@@ -207,38 +258,6 @@
207
  <div id="pickaxeSlot" class="pickaxe-slot" title="Pickaxe (press F to equip)">⛏️</div>
208
  <div id="hudGear" aria-label="gear"></div>
209
  </div>
210
-
211
- <!-- Mobile Controls -->
212
- <div id="mobileControls" aria-hidden="true">
213
- <div style="display:flex; justify-content:space-between;">
214
- <div class="mobile-left">
215
- <div class="dpad" id="dpad">
216
- <button class="up" data-dir="up">▲</button>
217
- <button class="down" data-dir="down">▼</button>
218
- <button class="left" data-dir="left">◀</button>
219
- <button class="right" data-dir="right">▶</button>
220
- </div>
221
- </div>
222
-
223
- <div class="mobile-right" style="align-items:flex-end; gap:14px;">
224
- <div style="display:flex; flex-direction:column; align-items:flex-end;">
225
- <div class="mobile-actions" id="mobileActions">
226
- <div class="medkit-wrap">
227
- <canvas class="medkit-progress" id="medkitProgress" width="64" height="64" style="border-radius:12px;"></canvas>
228
- <button id="fireBtn" class="action-btn" style="width:64px;height:64px;">FIRE</button>
229
- </div>
230
- <div class="small-actions">
231
- <button id="interactBtn" class="action-btn" style="width:64px;height:44px;">USE</button>
232
- <button id="buildBtn" class="action-btn" style="width:64px;height:44px;">BUILD</button>
233
- <button id="reloadBtn" class="action-btn" style="width:64px;height:44px;">RLD</button>
234
- <button id="equipBtn" class="action-btn" style="width:64px;height:44px;">EQ</button>
235
- </div>
236
- </div>
237
- </div>
238
- </div>
239
- </div>
240
- </div>
241
-
242
  </div>
243
  </div>
244
  </div>
@@ -261,22 +280,13 @@
261
  const continueBtn = document.getElementById('continueBtn');
262
  const biomeGrid = document.getElementById('biomeGrid');
263
 
264
- // Mobile controls elements
265
- const mobileControls = document.getElementById('mobileControls');
266
- const dpad = document.getElementById('dpad');
267
- const fireBtn = document.getElementById('fireBtn');
268
- const interactBtn = document.getElementById('interactBtn');
269
- const buildBtn = document.getElementById('buildBtn');
270
- const reloadBtn = document.getElementById('reloadBtn');
271
- const equipBtn = document.getElementById('equipBtn');
272
- const medkitCanvas = document.getElementById('medkitProgress');
273
- const medkitCtx = medkitCanvas.getContext('2d');
274
-
275
- // Loading screen elements and minimap
276
  const loadingScreen = document.getElementById('loadingScreen');
277
  const loadingTipEl = document.getElementById('loadingTip');
278
  const loadingProgressEl = document.getElementById('loadingProgress');
279
  const loadingTimerText = document.getElementById('loadingTimerText');
 
 
280
  const minimapCanvas = document.getElementById('minimapCanvas');
281
  const miniCtx = minimapCanvas.getContext('2d');
282
  let miniTerrainCache = null;
@@ -289,22 +299,14 @@
289
  const ctn = document.getElementById('canvasContainer');
290
  canvas.width = Math.max(900, Math.floor(ctn.clientWidth));
291
  canvas.height = Math.max(560, Math.floor(ctn.clientHeight));
 
292
  minimapCanvas.width = 220;
293
  minimapCanvas.height = 140;
294
  cameraUpdate();
295
- miniTerrainCache = null;
296
  }
297
  window.addEventListener('resize', resizeCanvas);
298
 
299
- // Detect mobile/touch
300
- const isMobile = ('ontouchstart' in window) || navigator.maxTouchPoints > 0;
301
- if (isMobile){
302
- mobileControls.style.display = 'block';
303
- mobileControls.setAttribute('aria-hidden', 'false');
304
- // hide cursor for better mobile feel
305
- canvas.style.cursor = 'none';
306
- }
307
-
308
  // Player
309
  const player = {
310
  id:'player', x: WORLD.width/2, y: WORLD.height/2, radius:16, angle:0, speed:220,
@@ -313,9 +315,8 @@
313
  selectedSlot:0,
314
  equippedIndex: -1,
315
  lastShot:0, lastMelee:0,
316
- // medkit hold state
317
- ePressStart: 0, // timestamp when E/use started (keyboard or touch)
318
- ePressApplied: false // whether medkit has been applied for current press
319
  };
320
 
321
  // Input
@@ -326,42 +327,33 @@
326
 
327
  window.addEventListener('keydown',(e)=>{
328
  const k = e.key.toLowerCase();
329
- if (k in keys) keys[k] = true;
330
- // handle medkit hold start on keydown
331
- if (k === 'e'){
332
- player.ePressStart = performance.now();
333
- player.ePressApplied = false;
334
- }
335
- if (['1','2','3','4','5'].includes(k)) {
336
- const idx = parseInt(k)-1;
337
- player.selectedSlot = idx;
338
- equipSlot(idx);
339
- updateHUD();
340
- }
341
- if (k === 'f') {
342
- if (player.equippedIndex === player.selectedSlot) equipSlot(-1);
343
- else equipSlot(player.selectedSlot);
344
- updateHUD();
 
 
 
345
  }
346
- if (k === 'r') keys.r = true;
347
  });
348
 
349
  window.addEventListener('keyup',(e)=>{
350
  const k = e.key.toLowerCase();
351
  if (k in keys) keys[k] = false;
352
- // medkit / interact: if released quickly treat as tap interact, else if held long enough medkit already applied
353
- if (k === 'e'){
354
- const now = performance.now();
355
- const held = player.ePressStart ? (now - player.ePressStart) : 0;
356
- if (!player.ePressApplied){
357
- if (held < 3000){
358
- interactNearby();
359
- } // else medkit applied automatically earlier
360
- }
361
- player.ePressStart = 0;
362
- player.ePressApplied = false;
363
- }
364
- if (k === 'q') { keys.q = false; }
365
  });
366
 
367
  canvas.addEventListener('mousemove',(e)=>{
@@ -383,70 +375,7 @@
383
  });
384
  window.addEventListener('mouseup', ()=> mouse.down = false);
385
 
386
- // Mobile controls touch handlers
387
- function bindTouchButton(el, onStart, onEnd){
388
- if (!el) return;
389
- el.addEventListener('touchstart', (ev) => { ev.preventDefault(); onStart && onStart(ev); }, { passive:false });
390
- el.addEventListener('touchend', (ev) => { ev.preventDefault(); onEnd && onEnd(ev); }, { passive:false });
391
- el.addEventListener('touchcancel', (ev) => { ev.preventDefault(); onEnd && onEnd(ev); }, { passive:false });
392
- }
393
-
394
- // D-pad buttons
395
- const dirButtons = dpad.querySelectorAll('button');
396
- dirButtons.forEach(btn => {
397
- const dir = btn.dataset.dir;
398
- bindTouchButton(btn,
399
- () => { // start
400
- if (dir === 'up') keys.w = true;
401
- if (dir === 'down') keys.s = true;
402
- if (dir === 'left') keys.a = true;
403
- if (dir === 'right') keys.d = true;
404
- },
405
- () => { // end
406
- if (dir === 'up') keys.w = false;
407
- if (dir === 'down') keys.s = false;
408
- if (dir === 'left') keys.a = false;
409
- if (dir === 'right') keys.d = false;
410
- }
411
- );
412
- });
413
-
414
- // FIRE: hold to fire (maps to mouse.down)
415
- bindTouchButton(fireBtn, () => { mouse.down = true; }, () => { mouse.down = false; });
416
-
417
- // INTERACT / USE: tap to interact, hold to use medkit
418
- bindTouchButton(interactBtn,
419
- (ev) => {
420
- player.ePressStart = performance.now();
421
- player.ePressApplied = false;
422
- },
423
- (ev) => {
424
- const now = performance.now();
425
- const held = player.ePressStart ? (now - player.ePressStart) : 0;
426
- if (!player.ePressApplied){
427
- if (held < 3000){
428
- interactNearby();
429
- }
430
- }
431
- player.ePressStart = 0;
432
- player.ePressApplied = false;
433
- }
434
- );
435
-
436
- // BUILD (Q)
437
- bindTouchButton(buildBtn, null, () => { tryBuild(); });
438
-
439
- // RELOAD (R)
440
- bindTouchButton(reloadBtn, null, () => { reloadEquipped(); });
441
-
442
- // EQUIP toggle (F)
443
- bindTouchButton(equipBtn, null, () => {
444
- if (player.equippedIndex === player.selectedSlot) equipSlot(-1);
445
- else equipSlot(player.selectedSlot);
446
- updateHUD();
447
- });
448
-
449
- // Entities and world arrays
450
  const bullets = [];
451
  const chests = [];
452
  const objects = [];
@@ -483,9 +412,11 @@
483
  return { type:'weapon', weapon: weapons[randInt(0, weapons.length)] };
484
  }
485
 
 
486
  const VIEW_RANGE = 1200;
487
  const SPAWN_PROTECT_MS = 1200;
488
 
 
489
  function getObjectRadius(obj){
490
  if (!obj) return 18;
491
  if (obj.type === 'wall') return 28;
@@ -574,6 +505,7 @@
574
  tempTarget: null,
575
  tempTargetExpiry: 0,
576
  prioritizeChestsUntil: 0,
 
577
  usingMedkit: false, usingMedkitStart: 0, usingMedkitUntil: 0, usingMedkitSlot: -1
578
  };
579
 
@@ -617,13 +549,9 @@
617
  function updateHUD(){
618
  const now = performance.now();
619
  let healthText = `${Math.max(0,Math.floor(player.health))}%`;
620
- // show medkit hold progress on HUD for keyboard or show "Healed" when applied
621
- if (player.ePressStart && !player.ePressApplied){
622
- const elapsed = Math.min(3000, now - player.ePressStart);
623
- const remaining = Math.ceil((3000 - elapsed)/1000);
624
- healthText += ` (Holding to use medkit: ${Math.max(0, (3000 - Math.floor(elapsed))/1000).toFixed(1)}s)`;
625
- } else if (player.ePressApplied){
626
- healthText += ' (Medkit used)';
627
  }
628
  hudHealthText.textContent = healthText;
629
  const slots = hudGear.querySelectorAll('.gear-slot');
@@ -705,10 +633,63 @@
705
  }
706
  }
707
 
708
- // Player interact & medkit usage (refactored: hold to use for 3s; tap to interact)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
709
  function interactNearby(){
710
  const sel = player.selectedSlot;
711
  const selItem = player.inventory[sel];
 
712
  const range = 56;
713
  for (const chest of chests){
714
  if (chest.opened) continue;
@@ -737,19 +718,6 @@
737
  }
738
  }
739
 
740
- function applyPlayerMedkitNow(){
741
- const sel = player.selectedSlot;
742
- const it = player.inventory[sel];
743
- if (!it || it.type !== 'medkit' || it.amount <= 0) return false;
744
- it.amount -= 1;
745
- player.health = Math.min(100, player.health + 50);
746
- if (it.amount <= 0) player.inventory[sel] = null;
747
- player.ePressApplied = true;
748
- player.ePressStart = 0;
749
- updateHUD();
750
- return true;
751
- }
752
-
753
  function pickupCollect(p){
754
  if (p.type === 'weapon'){
755
  let merged=false;
@@ -779,7 +747,7 @@
779
  }
780
  }
781
 
782
- // Enemy medkit helpers (unchanged)
783
  function enemyEquipBestWeapon(e){
784
  let bestIdx = -1;
785
  let bestScore = -Infinity;
@@ -803,6 +771,7 @@
803
  e.usingMedkitStart = now;
804
  e.usingMedkitUntil = now + 3000;
805
  e.usingMedkitSlot = s;
 
806
  e.nextHealTime = now + 5000;
807
  return true;
808
  }
@@ -876,6 +845,7 @@
876
  const it = e.inventory[s];
877
  if (it && it.type==='medkit'){ it.amount += p.amount;
878
  if (e.health < 60) {
 
879
  if (!e.usingMedkit) enemyStartMedkitUse(e, performance.now());
880
  }
881
  return;
@@ -910,6 +880,7 @@
910
  return true;
911
  }
912
 
 
913
  function hasLineOfSight(x1,y1,x2,y2){
914
  const vx = x2 - x1, vy = y2 - y1;
915
  const vlen2 = vx*vx + vy*vy;
@@ -946,6 +917,7 @@
946
  return closest;
947
  }
948
 
 
949
  function computeDetourWaypoint(fromX, fromY, blocker, goalX, goalY, padding = 12){
950
  if (!blocker) return null;
951
  let br = 0;
@@ -969,7 +941,7 @@
969
  return chosen;
970
  }
971
 
972
- // Bullets update with collisions (player & enemy & terrain)
973
  function bulletsUpdate(dt){
974
  for (let i=bullets.length-1;i>=0;i--){
975
  const b = bullets[i];
@@ -982,10 +954,10 @@
982
  if (b.shooter !== 'player'){
983
  const dPlayer = Math.hypot(player.x - b.x, player.y - b.y);
984
  if (dPlayer < hitRadiusPlayer){
 
985
  player.health -= b.dmg;
986
- // cancel medkit hold on taking damage
987
- player.ePressStart = 0;
988
- player.ePressApplied = false;
989
  if (player.health <= 0){ player.health = 0; playerDeath(); }
990
  bullets.splice(i,1);
991
  continue;
@@ -998,9 +970,12 @@
998
  if (e.health <= 0) continue;
999
  const d = Math.hypot(e.x - b.x, e.y - b.y);
1000
  if (d < 14 && b.shooter !== e.id){
 
1001
  e.health -= b.dmg;
1002
  e.lastAttackedTime = performance.now();
 
1003
  if (e.usingMedkit){
 
1004
  e.usingMedkit = false; e.usingMedkitSlot = -1; e.usingMedkitStart = 0; e.usingMedkitUntil = 0;
1005
  }
1006
  if (e.health <= 0){
@@ -1022,10 +997,12 @@
1022
  if (obj.dead) continue;
1023
  const rr = getObjectRadius(obj);
1024
  const d = Math.hypot(obj.x - b.x, obj.y - b.y);
 
1025
  if (d < rr + 2){
1026
  obj.hp -= b.dmg;
1027
  if (obj.hp <= 0){
1028
  obj.dead = true;
 
1029
  if (b.shooter === 'player') player.materials += (obj.type === 'wood' ? 3 : 6);
1030
  }
1031
  hitObj = obj;
@@ -1037,7 +1014,7 @@
1037
  continue;
1038
  }
1039
 
1040
- // 4) Hit chests
1041
  for (const chest of chests){
1042
  if (chest.opened) continue;
1043
  const d = Math.hypot(chest.x - b.x, chest.y - b.y);
@@ -1059,6 +1036,7 @@
1059
  }
1060
  }
1061
 
 
1062
  function findNearestChest(e, maxDist = 1200){
1063
  let best = null; let bd = Infinity;
1064
  for (const c of chests){ if (c.opened) continue; const d = Math.hypot(c.x - e.x, c.y - e.y); if (d < bd && d <= maxDist){ bd = d; best = c; } }
@@ -1075,12 +1053,14 @@
1075
  return best;
1076
  }
1077
 
 
1078
  function updateEnemies(dt, now){
1079
  const minSeparation = 20;
1080
  for (const e of enemies){
1081
  if (e.health <= 0) continue;
1082
  if (!e.spawnSafeUntil) e.spawnSafeUntil = performance.now() + SPAWN_PROTECT_MS;
1083
 
 
1084
  for (const chest of chests){
1085
  if (chest.opened) continue;
1086
  const distChest = Math.hypot(chest.x - e.x, chest.y - e.y);
@@ -1099,16 +1079,20 @@
1099
  }
1100
  }
1101
 
 
1102
  if (e.usingMedkit){
 
1103
  if (e.lastAttackedTime > e.usingMedkitStart){
1104
  e.usingMedkit = false; e.usingMedkitSlot = -1; e.usingMedkitStart = 0; e.usingMedkitUntil = 0;
1105
  } else if (now >= e.usingMedkitUntil){
1106
  applyEnemyMedkitUseNow(e);
1107
  } else {
 
1108
  continue;
1109
  }
1110
  }
1111
 
 
1112
  if (storm.active){
1113
  const distToSafeCenter = Math.hypot(e.x - storm.centerX, e.y - storm.centerY);
1114
  if (distToSafeCenter > storm.radius){
@@ -1173,10 +1157,12 @@
1173
  }
1174
  }
1175
 
 
1176
  if (now - e.lastAttackedTime < 4000) e.state = 'combat';
1177
  if (e.state === 'gather') e.gatherTimeLeft -= dt;
1178
 
1179
  if (e.health < 60 && now >= (e.nextHealTime || 0) && !e.usingMedkit){
 
1180
  if (enemyStartMedkitUse(e, now)){
1181
  continue;
1182
  }
@@ -1261,6 +1247,7 @@
1261
  continue;
1262
  }
1263
 
 
1264
  if (e.equippedIndex === -1) enemyEquipBestWeapon(e);
1265
  if (e.reloadPending){
1266
  if (now >= e.reloadingUntil){
@@ -1466,6 +1453,7 @@
1466
  }
1467
  }
1468
 
 
1469
  const storm = { maxRadius: 2400, radius:2400, centerX: WORLD.width/2, centerY: WORLD.height/2, damagePerSecond:1, closingSpeed: 0.6, active:false };
1470
  let stormDamageAccumulator = 0;
1471
  function playerInStorm(){ return Math.hypot(player.x - storm.centerX, player.y - storm.centerY) > storm.radius; }
@@ -1480,7 +1468,7 @@
1480
  while (stormDamageAccumulator >= 1){
1481
  stormDamageAccumulator -=1;
1482
  player.health -= 1;
1483
- player.ePressStart = 0; player.ePressApplied = false;
1484
  if (player.health <= 0){ player.health = 0; playerDeath(); }
1485
  }
1486
  } else stormDamageAccumulator = 0;
@@ -1505,7 +1493,8 @@
1505
  }
1506
  }
1507
 
1508
- // Drawing functions (kept from prior code)
 
1509
  function drawWorld(){
1510
  const TILE = 600;
1511
  const cols = Math.ceil(WORLD.width / TILE);
@@ -1713,7 +1702,7 @@
1713
 
1714
  function drawCrosshair(){}
1715
 
1716
- // Minimap functions
1717
  function buildMiniTerrainCache(){
1718
  const mw = minimapCanvas.width;
1719
  const mh = minimapCanvas.height;
@@ -1806,36 +1795,6 @@
1806
  miniCtx.restore();
1807
  }
1808
 
1809
- // Medkit progress draw (mobile ring)
1810
- function drawMedkitProgress(){
1811
- const ctxm = medkitCtx;
1812
- ctxm.clearRect(0,0,64,64);
1813
- const now = performance.now();
1814
- let progress = 0;
1815
- if (player.ePressStart && !player.ePressApplied){
1816
- progress = Math.min(1, (now - player.ePressStart) / 3000);
1817
- } else if (player.ePressApplied){
1818
- progress = 1;
1819
- }
1820
- // background circle
1821
- ctxm.beginPath();
1822
- ctxm.arc(32,32,28,0,Math.PI*2);
1823
- ctxm.fillStyle = 'rgba(255,255,255,0.02)';
1824
- ctxm.fill();
1825
- // progress arc
1826
- ctxm.beginPath();
1827
- ctxm.strokeStyle = 'rgba(255,107,107,0.95)';
1828
- ctxm.lineWidth = 6;
1829
- ctxm.lineCap = 'round';
1830
- ctxm.arc(32,32,24,-Math.PI/2, -Math.PI/2 + (Math.PI*2*progress));
1831
- ctxm.stroke();
1832
- // center label
1833
- ctxm.fillStyle = '#fff';
1834
- ctxm.font = '10px monospace';
1835
- ctxm.textAlign = 'center';
1836
- ctxm.fillText('USE', 32, 36);
1837
- }
1838
-
1839
  // Main loop
1840
  let lastTime = 0;
1841
  function gameLoop(ts){
@@ -1863,11 +1822,8 @@
1863
  if (isCollidingSolid(player.x, player.y, player.radius)){
1864
  player.y = oldY;
1865
  }
1866
- // If player moves while holding medkit, cancel the hold
1867
- if (player.ePressStart){
1868
- player.ePressStart = 0;
1869
- player.ePressApplied = false;
1870
- }
1871
  }
1872
  player.x = Math.max(16, Math.min(WORLD.width-16, player.x));
1873
  player.y = Math.max(16, Math.min(WORLD.height-16, player.y));
@@ -1899,25 +1855,9 @@
1899
  }
1900
 
1901
  if (keys.r) { reloadEquipped(); keys.r = false; }
 
1902
  if (keys.q){ tryBuild(); keys.q = false; }
1903
 
1904
- // Handle player medkit hold: if holding and not applied and reached duration, apply automatically
1905
- if (player.ePressStart && !player.ePressApplied){
1906
- const now = performance.now();
1907
- const elapsed = now - player.ePressStart;
1908
- const sel = player.selectedSlot;
1909
- const it = player.inventory[sel];
1910
- if (it && it.type === 'medkit'){
1911
- if (elapsed >= 3000){
1912
- // apply medkit
1913
- applyPlayerMedkitNow();
1914
- }
1915
- } else {
1916
- // if not medkit in slot, cancel hold and treat as simple interact on release
1917
- // (we don't auto-interact here)
1918
- }
1919
- }
1920
-
1921
  updateEnemies(dt, performance.now());
1922
  bulletsUpdate(dt);
1923
 
@@ -1939,10 +1879,6 @@
1939
 
1940
  if (storm.active && playerInStorm()) stormWarning.classList.remove('hidden'); else stormWarning.classList.add('hidden');
1941
 
1942
- if (isMobile){
1943
- drawMedkitProgress();
1944
- }
1945
-
1946
  requestAnimationFrame(gameLoop);
1947
  }
1948
 
@@ -1980,7 +1916,7 @@
1980
  player.health = 100; player.armor = 0; player.kills = 0; player.materials = 0;
1981
  player.inventory = [null,null,null,null,null];
1982
  player.selectedSlot = 0; player.equippedIndex = -1; player.lastShot = 0; player.lastMelee = 0;
1983
- player.ePressStart = 0; player.ePressApplied = false;
1984
 
1985
  populateWorld();
1986
  initHUD();
@@ -2028,7 +1964,8 @@
2028
  function endGame(){ gameActive = false; alert('Match over!'); }
2029
  function playerDeath(){
2030
  gameActive = false;
2031
- player.ePressStart = 0; player.ePressApplied = false;
 
2032
  deathScreen.classList.remove('hidden');
2033
  }
2034
 
@@ -2036,7 +1973,7 @@
2036
  goHomeBtn.addEventListener('click', ()=>{ victoryScreen.classList.add('hidden'); gameScreen.classList.add('hidden'); landingScreen.classList.remove('hidden'); });
2037
  continueBtn.addEventListener('click', ()=>{ victoryScreen.classList.add('hidden'); startGame(selectedBiome); });
2038
 
2039
- // Loading tips
2040
  const loadingTips = [
2041
  "Stick to cover when approaching buildings — open areas get you killed.",
2042
  "Loot chests quickly and move — enemies can hear opening animations.",
 
3
  <head>
4
  <meta charset="utf-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>BattleZone Royale - Minimap Added</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <script src="https://unpkg.com/feather-icons"></script>
9
  <style>
 
26
  .biome-selected { outline: 3px solid rgba(96,165,250,0.9); }
27
 
28
  /* Loading screen */
29
+ #loadingScreen {
 
 
 
30
  position: absolute;
31
+ inset: 0;
32
+ z-index: 80;
 
 
 
33
  display: none;
34
+ align-items: center;
35
+ justify-content: center;
36
+ overflow: hidden;
37
+ color: #fff;
38
+ padding: 24px;
39
+ box-sizing: border-box;
40
+ }
41
+
42
+ /* animated gradient background */
43
+ .loading-bg {
44
+ position: absolute;
45
+ inset: -20%;
46
+ background: linear-gradient(120deg, #0b1220 0%, #123049 30%, #5b2a6a 60%, #2f4b2f 100%);
47
+ filter: blur(20px) saturate(1.1);
48
+ animation: bgShift 18s linear infinite;
49
+ transform: scale(1.1);
50
+ opacity: 0.85;
51
+ }
52
+ @keyframes bgShift {
53
+ 0% { filter: hue-rotate(0deg) blur(20px); transform: scale(1.06) rotate(0.01deg); }
54
+ 25% { filter: hue-rotate(45deg) blur(18px); transform: scale(1.08) rotate(0.02deg); }
55
+ 50% { filter: hue-rotate(90deg) blur(22px); transform: scale(1.04) rotate(-0.01deg); }
56
+ 75% { filter: hue-rotate(180deg) blur(20px); transform: scale(1.07) rotate(0.01deg); }
57
+ 100% { filter: hue-rotate(360deg) blur(20px); transform: scale(1.06) rotate(-0.02deg); }
58
+ }
59
+
60
+ .loading-card {
61
+ position: relative;
62
+ z-index: 2;
63
+ max-width: 880px;
64
+ width: calc(100% - 48px);
65
+ background: rgba(6,10,18,0.55);
66
+ border: 1px solid rgba(255,255,255,0.06);
67
+ box-shadow: 0 12px 40px rgba(0,0,0,0.7);
68
+ border-radius: 12px;
69
+ padding: 20px;
70
+ display:flex;
71
+ gap:18px;
72
+ align-items:center;
73
+ }
74
+
75
+ .loading-left {
76
+ flex: 1;
77
+ min-width: 260px;
78
+ display:flex; flex-direction:column; gap:10px;
79
+ }
80
+ .loading-right {
81
+ width: 260px;
82
+ display:flex; flex-direction:column; gap:12px; align-items:center;
83
+ }
84
+
85
+ .loader-spinner {
86
+ width: 84px; height:84px; border-radius:50%; position:relative;
87
+ display:flex; align-items:center; justify-content:center;
88
  }
89
+ .ring {
90
+ position:absolute; inset:0; border-radius:50%; box-shadow: inset 0 0 24px rgba(255,255,255,0.02);
91
+ border: 6px solid rgba(255,255,255,0.06);
92
+ animation: ringRotate 1.8s linear infinite;
 
93
  }
94
+ .ring::after {
95
+ content:'';
96
+ position:absolute; width:16px; height:16px; right:8px; top:50%; transform: translateY(-50%);
97
+ background: linear-gradient(90deg,#ffd86b,#8ef0ff);
98
+ border-radius:50%;
 
 
 
 
 
 
 
 
 
 
 
99
  }
100
+ @keyframes ringRotate { to { transform: rotate(360deg); } }
101
+ .loader-dots { display:flex; gap:6px; }
102
+ .dot { width:10px; height:10px; border-radius:50%; background:#ffd86b; opacity:0.6; animation: dotPulse 1.2s infinite; }
103
+ .dot:nth-child(2){ animation-delay: 0.15s; background:#8ef0ff; }
104
+ .dot:nth-child(3){ animation-delay: 0.3s; background:#ff9fb8; }
105
+ @keyframes dotPulse { 0%{ transform: scale(.8); opacity:0.4 } 50%{ transform: scale(1.2); opacity:1 } 100%{ transform: scale(.8); opacity:0.4 } }
106
+
107
+ .tips { font-size:14px; color:#e8eef7; background: rgba(0,0,0,0.18); padding:10px; border-radius:8px; line-height:1.3; }
108
+ .loading-title { font-size:18px; font-weight:800; color:#ffd86b; display:flex; align-items:center; gap:8px; }
109
+
110
+ .progress-wrap { width:100%; background: rgba(255,255,255,0.04); height:14px; border-radius:8px; overflow:hidden; }
111
+ .progress-bar { height:100%; width:0%; background: linear-gradient(90deg,#ffd86b,#8ef0ff); transition: width 0.12s linear; }
112
 
113
+ .loading-count { font-size:12px; color:#dfe9f9; }
114
+
115
+ .disabled-pane { pointer-events: none; opacity: 0.6; filter: grayscale(12%); }
116
+
117
+ @media (max-width: 820px){
118
+ .loading-card { flex-direction:column; align-items:center; text-align:center; }
119
+ .loading-right { width:100%; }
120
+ }
121
  </style>
122
  </head>
123
  <body>
 
157
  <li><strong>WASD</strong> — Move</li>
158
  <li><strong>Mouse</strong> — Aim</li>
159
  <li><strong>Left Click</strong> — Shoot (if weapon equipped or in selected slot) or pickaxe melee</li>
160
+ <li><strong>E</strong> — Use medkit in selected slot OR Interact / Loot</li>
161
  <li><strong>Q</strong> — Build (costs 10 materials)</li>
162
  <li><strong>R</strong> — Reload selected weapon</li>
163
  <li><strong>1-5</strong> — Select inventory slot (also equips it)</li>
 
258
  <div id="pickaxeSlot" class="pickaxe-slot" title="Pickaxe (press F to equip)">⛏️</div>
259
  <div id="hudGear" aria-label="gear"></div>
260
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
  </div>
262
  </div>
263
  </div>
 
280
  const continueBtn = document.getElementById('continueBtn');
281
  const biomeGrid = document.getElementById('biomeGrid');
282
 
283
+ // Loading screen elements
 
 
 
 
 
 
 
 
 
 
 
284
  const loadingScreen = document.getElementById('loadingScreen');
285
  const loadingTipEl = document.getElementById('loadingTip');
286
  const loadingProgressEl = document.getElementById('loadingProgress');
287
  const loadingTimerText = document.getElementById('loadingTimerText');
288
+
289
+ // Minimap elements and cache
290
  const minimapCanvas = document.getElementById('minimapCanvas');
291
  const miniCtx = minimapCanvas.getContext('2d');
292
  let miniTerrainCache = null;
 
299
  const ctn = document.getElementById('canvasContainer');
300
  canvas.width = Math.max(900, Math.floor(ctn.clientWidth));
301
  canvas.height = Math.max(560, Math.floor(ctn.clientHeight));
302
+ // keep minimap internal pixel buffer consistent
303
  minimapCanvas.width = 220;
304
  minimapCanvas.height = 140;
305
  cameraUpdate();
306
+ miniTerrainCache = null; // rebuild on resize
307
  }
308
  window.addEventListener('resize', resizeCanvas);
309
 
 
 
 
 
 
 
 
 
 
310
  // Player
311
  const player = {
312
  id:'player', x: WORLD.width/2, y: WORLD.height/2, radius:16, angle:0, speed:220,
 
315
  selectedSlot:0,
316
  equippedIndex: -1,
317
  lastShot:0, lastMelee:0,
318
+ // medkit usage state
319
+ usingMedkit: false, usingMedkitUntil: 0, usingMedkitStart: 0, usingMedkitTimeout: null
 
320
  };
321
 
322
  // Input
 
327
 
328
  window.addEventListener('keydown',(e)=>{
329
  const k = e.key.toLowerCase();
330
+ if (gameActive){
331
+ if (k in keys) keys[k] = true;
332
+ if (['1','2','3','4','5'].includes(k)) {
333
+ const idx = parseInt(k)-1;
334
+ player.selectedSlot = idx;
335
+ equipSlot(idx);
336
+ updateHUD();
337
+ }
338
+ if (k === 'f') {
339
+ if (player.equippedIndex === player.selectedSlot) equipSlot(-1);
340
+ else equipSlot(player.selectedSlot);
341
+ updateHUD();
342
+ }
343
+ if (k === 'r') keys.r = true;
344
+ } else {
345
+ if (['1','2','3','4','5'].includes(k)) {
346
+ player.selectedSlot = parseInt(k)-1;
347
+ updateHUD();
348
+ }
349
  }
 
350
  });
351
 
352
  window.addEventListener('keyup',(e)=>{
353
  const k = e.key.toLowerCase();
354
  if (k in keys) keys[k] = false;
355
+ if (k === 'e') keys.e = true;
356
+ if (k === 'q') keys.q = true;
 
 
 
 
 
 
 
 
 
 
 
357
  });
358
 
359
  canvas.addEventListener('mousemove',(e)=>{
 
375
  });
376
  window.addEventListener('mouseup', ()=> mouse.down = false);
377
 
378
+ // Entities
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
379
  const bullets = [];
380
  const chests = [];
381
  const objects = [];
 
412
  return { type:'weapon', weapon: weapons[randInt(0, weapons.length)] };
413
  }
414
 
415
+ // Behaviour tuning
416
  const VIEW_RANGE = 1200;
417
  const SPAWN_PROTECT_MS = 1200;
418
 
419
+ // collision helpers
420
  function getObjectRadius(obj){
421
  if (!obj) return 18;
422
  if (obj.type === 'wall') return 28;
 
505
  tempTarget: null,
506
  tempTargetExpiry: 0,
507
  prioritizeChestsUntil: 0,
508
+ // medkit state for enemies
509
  usingMedkit: false, usingMedkitStart: 0, usingMedkitUntil: 0, usingMedkitSlot: -1
510
  };
511
 
 
549
  function updateHUD(){
550
  const now = performance.now();
551
  let healthText = `${Math.max(0,Math.floor(player.health))}%`;
552
+ if (player.usingMedkit){
553
+ const remaining = Math.max(0, Math.ceil((player.usingMedkitUntil - now)/1000));
554
+ healthText += ` (Healing ${remaining}s)`;
 
 
 
 
555
  }
556
  hudHealthText.textContent = healthText;
557
  const slots = hudGear.querySelectorAll('.gear-slot');
 
633
  }
634
  }
635
 
636
+ // Player interact & medkit usage
637
+ function attemptUseOrInteract(){
638
+ const sel = player.selectedSlot;
639
+ const selItem = player.inventory[sel];
640
+ if (selItem && selItem.type === 'medkit'){
641
+ // toggle or start medkit usage
642
+ if (player.usingMedkit){
643
+ cancelPlayerMedkitUse();
644
+ } else {
645
+ startPlayerMedkitUse(sel);
646
+ }
647
+ } else {
648
+ interactNearby();
649
+ }
650
+ }
651
+
652
+ function startPlayerMedkitUse(slot){
653
+ const it = player.inventory[slot];
654
+ if (!it || it.type !== 'medkit' || it.amount <= 0) return;
655
+ if (player.usingMedkit) return;
656
+ const now = performance.now();
657
+ player.usingMedkit = true;
658
+ player.usingMedkitStart = now;
659
+ player.usingMedkitUntil = now + 3000; // 3 seconds
660
+ // set a timeout to apply heal
661
+ player.usingMedkitTimeout = setTimeout(()=> {
662
+ // ensure still using and item present
663
+ if (!player.usingMedkit) return;
664
+ const cur = player.inventory[slot];
665
+ if (cur && cur.type === 'medkit'){
666
+ cur.amount -= 1;
667
+ player.health = Math.min(100, player.health + 50);
668
+ if (cur.amount <= 0) player.inventory[slot] = null;
669
+ }
670
+ player.usingMedkit = false;
671
+ player.usingMedkitTimeout = null;
672
+ updateHUD();
673
+ }, 3000);
674
+ updateHUD();
675
+ }
676
+
677
+ function cancelPlayerMedkitUse(){
678
+ if (!player.usingMedkit) return;
679
+ player.usingMedkit = false;
680
+ player.usingMedkitStart = 0;
681
+ player.usingMedkitUntil = 0;
682
+ if (player.usingMedkitTimeout){
683
+ clearTimeout(player.usingMedkitTimeout);
684
+ player.usingMedkitTimeout = null;
685
+ }
686
+ updateHUD();
687
+ }
688
+
689
  function interactNearby(){
690
  const sel = player.selectedSlot;
691
  const selItem = player.inventory[sel];
692
+ // medkit immediate use removed — now 3s usage handled above
693
  const range = 56;
694
  for (const chest of chests){
695
  if (chest.opened) continue;
 
718
  }
719
  }
720
 
 
 
 
 
 
 
 
 
 
 
 
 
 
721
  function pickupCollect(p){
722
  if (p.type === 'weapon'){
723
  let merged=false;
 
747
  }
748
  }
749
 
750
+ // Enemy helpers (medkit usage now takes 3s)
751
  function enemyEquipBestWeapon(e){
752
  let bestIdx = -1;
753
  let bestScore = -Infinity;
 
771
  e.usingMedkitStart = now;
772
  e.usingMedkitUntil = now + 3000;
773
  e.usingMedkitSlot = s;
774
+ // set nextHealTime to prevent immediate re-trigger
775
  e.nextHealTime = now + 5000;
776
  return true;
777
  }
 
845
  const it = e.inventory[s];
846
  if (it && it.type==='medkit'){ it.amount += p.amount;
847
  if (e.health < 60) {
848
+ // start medkit use if not already using
849
  if (!e.usingMedkit) enemyStartMedkitUse(e, performance.now());
850
  }
851
  return;
 
880
  return true;
881
  }
882
 
883
+ // LOS helpers
884
  function hasLineOfSight(x1,y1,x2,y2){
885
  const vx = x2 - x1, vy = y2 - y1;
886
  const vlen2 = vx*vx + vy*vy;
 
917
  return closest;
918
  }
919
 
920
+ // New helper: compute detour waypoint around blocker
921
  function computeDetourWaypoint(fromX, fromY, blocker, goalX, goalY, padding = 12){
922
  if (!blocker) return null;
923
  let br = 0;
 
941
  return chosen;
942
  }
943
 
944
+ // Bullets update (rewritten for consistent collision handling)
945
  function bulletsUpdate(dt){
946
  for (let i=bullets.length-1;i>=0;i--){
947
  const b = bullets[i];
 
954
  if (b.shooter !== 'player'){
955
  const dPlayer = Math.hypot(player.x - b.x, player.y - b.y);
956
  if (dPlayer < hitRadiusPlayer){
957
+ // apply damage
958
  player.health -= b.dmg;
959
+ // cancel medkit usage if player is healing
960
+ if (player.usingMedkit) cancelPlayerMedkitUse();
 
961
  if (player.health <= 0){ player.health = 0; playerDeath(); }
962
  bullets.splice(i,1);
963
  continue;
 
970
  if (e.health <= 0) continue;
971
  const d = Math.hypot(e.x - b.x, e.y - b.y);
972
  if (d < 14 && b.shooter !== e.id){
973
+ // apply damage
974
  e.health -= b.dmg;
975
  e.lastAttackedTime = performance.now();
976
+ // cancel enemy medkit if they are healing
977
  if (e.usingMedkit){
978
+ // if they were using medkit, cancel it when hit
979
  e.usingMedkit = false; e.usingMedkitSlot = -1; e.usingMedkitStart = 0; e.usingMedkitUntil = 0;
980
  }
981
  if (e.health <= 0){
 
997
  if (obj.dead) continue;
998
  const rr = getObjectRadius(obj);
999
  const d = Math.hypot(obj.x - b.x, obj.y - b.y);
1000
+ // use a small tolerance so bullets clearly collide
1001
  if (d < rr + 2){
1002
  obj.hp -= b.dmg;
1003
  if (obj.hp <= 0){
1004
  obj.dead = true;
1005
+ // reward materials if player shot the object
1006
  if (b.shooter === 'player') player.materials += (obj.type === 'wood' ? 3 : 6);
1007
  }
1008
  hitObj = obj;
 
1014
  continue;
1015
  }
1016
 
1017
+ // 4) Hit chests (if wanted bullets can open chests) — keep old behaviour
1018
  for (const chest of chests){
1019
  if (chest.opened) continue;
1020
  const d = Math.hypot(chest.x - b.x, chest.y - b.y);
 
1036
  }
1037
  }
1038
 
1039
+ // Helpers to find nearest
1040
  function findNearestChest(e, maxDist = 1200){
1041
  let best = null; let bd = Infinity;
1042
  for (const c of chests){ if (c.opened) continue; const d = Math.hypot(c.x - e.x, c.y - e.y); if (d < bd && d <= maxDist){ bd = d; best = c; } }
 
1053
  return best;
1054
  }
1055
 
1056
+ // Enemy AI (with medkit use delay)
1057
  function updateEnemies(dt, now){
1058
  const minSeparation = 20;
1059
  for (const e of enemies){
1060
  if (e.health <= 0) continue;
1061
  if (!e.spawnSafeUntil) e.spawnSafeUntil = performance.now() + SPAWN_PROTECT_MS;
1062
 
1063
+ // Auto-open chests on proximity
1064
  for (const chest of chests){
1065
  if (chest.opened) continue;
1066
  const distChest = Math.hypot(chest.x - e.x, chest.y - e.y);
 
1079
  }
1080
  }
1081
 
1082
+ // Handle enemy medkit usage finishing/cancelling
1083
  if (e.usingMedkit){
1084
+ // if they were attacked while using, cancel
1085
  if (e.lastAttackedTime > e.usingMedkitStart){
1086
  e.usingMedkit = false; e.usingMedkitSlot = -1; e.usingMedkitStart = 0; e.usingMedkitUntil = 0;
1087
  } else if (now >= e.usingMedkitUntil){
1088
  applyEnemyMedkitUseNow(e);
1089
  } else {
1090
+ // still using medkit; don't do other heavy actions
1091
  continue;
1092
  }
1093
  }
1094
 
1095
+ // STORM behavior (unchanged)
1096
  if (storm.active){
1097
  const distToSafeCenter = Math.hypot(e.x - storm.centerX, e.y - storm.centerY);
1098
  if (distToSafeCenter > storm.radius){
 
1157
  }
1158
  }
1159
 
1160
+ // NORMAL behavior
1161
  if (now - e.lastAttackedTime < 4000) e.state = 'combat';
1162
  if (e.state === 'gather') e.gatherTimeLeft -= dt;
1163
 
1164
  if (e.health < 60 && now >= (e.nextHealTime || 0) && !e.usingMedkit){
1165
+ // start medkit use (3s) if possible
1166
  if (enemyStartMedkitUse(e, now)){
1167
  continue;
1168
  }
 
1247
  continue;
1248
  }
1249
 
1250
+ // Combat logic remains (omitted here for brevity but preserved)
1251
  if (e.equippedIndex === -1) enemyEquipBestWeapon(e);
1252
  if (e.reloadPending){
1253
  if (now >= e.reloadingUntil){
 
1453
  }
1454
  }
1455
 
1456
+ // Storm
1457
  const storm = { maxRadius: 2400, radius:2400, centerX: WORLD.width/2, centerY: WORLD.height/2, damagePerSecond:1, closingSpeed: 0.6, active:false };
1458
  let stormDamageAccumulator = 0;
1459
  function playerInStorm(){ return Math.hypot(player.x - storm.centerX, player.y - storm.centerY) > storm.radius; }
 
1468
  while (stormDamageAccumulator >= 1){
1469
  stormDamageAccumulator -=1;
1470
  player.health -= 1;
1471
+ if (player.usingMedkit) cancelPlayerMedkitUse();
1472
  if (player.health <= 0){ player.health = 0; playerDeath(); }
1473
  }
1474
  } else stormDamageAccumulator = 0;
 
1493
  }
1494
  }
1495
 
1496
+ // Drawing functions (unchanged, omitted here for brevity)...
1497
+ // (All draw functions from the previous code remain unchanged and are present below.)
1498
  function drawWorld(){
1499
  const TILE = 600;
1500
  const cols = Math.ceil(WORLD.width / TILE);
 
1702
 
1703
  function drawCrosshair(){}
1704
 
1705
+ // Minimap functions (unchanged)
1706
  function buildMiniTerrainCache(){
1707
  const mw = minimapCanvas.width;
1708
  const mh = minimapCanvas.height;
 
1795
  miniCtx.restore();
1796
  }
1797
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1798
  // Main loop
1799
  let lastTime = 0;
1800
  function gameLoop(ts){
 
1822
  if (isCollidingSolid(player.x, player.y, player.radius)){
1823
  player.y = oldY;
1824
  }
1825
+ // If player moves while using medkit, cancel use
1826
+ if (player.usingMedkit && (dx !== 0 || dy !== 0)) cancelPlayerMedkitUse();
 
 
 
1827
  }
1828
  player.x = Math.max(16, Math.min(WORLD.width-16, player.x));
1829
  player.y = Math.max(16, Math.min(WORLD.height-16, player.y));
 
1855
  }
1856
 
1857
  if (keys.r) { reloadEquipped(); keys.r = false; }
1858
+ if (keys.e){ attemptUseOrInteract(); keys.e = false; }
1859
  if (keys.q){ tryBuild(); keys.q = false; }
1860
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1861
  updateEnemies(dt, performance.now());
1862
  bulletsUpdate(dt);
1863
 
 
1879
 
1880
  if (storm.active && playerInStorm()) stormWarning.classList.remove('hidden'); else stormWarning.classList.add('hidden');
1881
 
 
 
 
 
1882
  requestAnimationFrame(gameLoop);
1883
  }
1884
 
 
1916
  player.health = 100; player.armor = 0; player.kills = 0; player.materials = 0;
1917
  player.inventory = [null,null,null,null,null];
1918
  player.selectedSlot = 0; player.equippedIndex = -1; player.lastShot = 0; player.lastMelee = 0;
1919
+ player.usingMedkit = false; player.usingMedkitTimeout = null;
1920
 
1921
  populateWorld();
1922
  initHUD();
 
1964
  function endGame(){ gameActive = false; alert('Match over!'); }
1965
  function playerDeath(){
1966
  gameActive = false;
1967
+ // cancel medkit use on death
1968
+ cancelPlayerMedkitUse();
1969
  deathScreen.classList.remove('hidden');
1970
  }
1971
 
 
1973
  goHomeBtn.addEventListener('click', ()=>{ victoryScreen.classList.add('hidden'); gameScreen.classList.add('hidden'); landingScreen.classList.remove('hidden'); });
1974
  continueBtn.addEventListener('click', ()=>{ victoryScreen.classList.add('hidden'); startGame(selectedBiome); });
1975
 
1976
+ // Loading screen logic (unchanged)
1977
  const loadingTips = [
1978
  "Stick to cover when approaching buildings — open areas get you killed.",
1979
  "Loot chests quickly and move — enemies can hear opening animations.",