bbc123321 commited on
Commit
0329574
·
verified ·
1 Parent(s): 3408a9f

Manual changes saved

Browse files
Files changed (1) hide show
  1. index.html +167 -99
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 - Full</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <script src="https://unpkg.com/feather-icons"></script>
9
  <style>
@@ -137,6 +137,13 @@
137
  -webkit-user-select:none;
138
  user-select:none;
139
  }
 
 
 
 
 
 
 
140
  .mobile-joystick .thumb {
141
  width:58px; height:58px; border-radius:50%;
142
  background: rgba(255,255,255,0.08);
@@ -144,6 +151,8 @@
144
  transform: translate(0,0);
145
  transition: transform 0.06s linear;
146
  }
 
 
147
  /* Buttons now reside left of pickaxe slot in HUD; this container is shown only on mobile */
148
  .mobile-buttons-hud {
149
  display:flex;
@@ -173,6 +182,11 @@
173
  .mobile-controls { display:none !important; }
174
  .mobile-buttons-hud { display:none !important; } /* hide HUD buttons on desktop too */
175
  }
 
 
 
 
 
176
  </style>
177
  </head>
178
  <body>
@@ -212,7 +226,7 @@
212
  <li><strong>WASD</strong> — Move</li>
213
  <li><strong>Mouse</strong> — Aim</li>
214
  <li><strong>Left Click</strong> — Shoot (if weapon equipped or in selected slot) or pickaxe melee</li>
215
- <li><strong>E</strong> — Use medkit in selected slot OR Interact / Loot</li>
216
  <li><strong>Q</strong> — Build (costs 10 materials)</li>
217
  <li><strong>R</strong> — Reload selected weapon</li>
218
  <li><strong>1-5</strong> — Select inventory slot (also equips it)</li>
@@ -322,11 +336,14 @@
322
  <div id="hudGear" aria-label="gear"></div>
323
  </div>
324
 
325
- <!-- Mobile Controls (joystick only) -->
326
  <div id="mobileControls" class="mobile-controls hidden" aria-hidden="true">
327
  <div id="mobileJoystick" class="mobile-joystick" role="application" aria-label="Movement joystick">
328
  <div id="joystickThumb" class="thumb"></div>
329
  </div>
 
 
 
330
  </div>
331
 
332
  </div>
@@ -334,6 +351,9 @@
334
  </div>
335
 
336
  <script>
 
 
 
337
  // DOM references
338
  const landingScreen = document.getElementById('landingScreen');
339
  const gameScreen = document.getElementById('gameScreen');
@@ -349,11 +369,9 @@
349
  const victoryScreen = document.getElementById('victoryScreen');
350
  const goHomeBtn = document.getElementById('goHomeBtn');
351
  const continueBtn = document.getElementById('continueBtn');
352
- const biomeGrid = document.getElementById('biomeGrid');
353
 
354
  // Mobile button references (in HUD)
355
  const mobileButtonsHud = document.getElementById('mobileButtonsHud');
356
- // Joystick container
357
  const mobileControls = document.getElementById('mobileControls');
358
 
359
  // Loading screen elements
@@ -375,9 +393,14 @@
375
  const ctn = document.getElementById('canvasContainer');
376
  canvas.width = Math.max(900, Math.floor(ctn.clientWidth));
377
  canvas.height = Math.max(560, Math.floor(ctn.clientHeight));
378
- // keep minimap internal pixel buffer consistent
379
- minimapCanvas.width = 220;
380
- minimapCanvas.height = 140;
 
 
 
 
 
381
  cameraUpdate();
382
  miniTerrainCache = null; // rebuild on resize
383
  }
@@ -391,8 +414,7 @@
391
  selectedSlot:0,
392
  equippedIndex: -1,
393
  lastShot:0, lastMelee:0,
394
- // medkit usage state
395
- usingMedkit: false, usingMedkitUntil: 0, usingMedkitStart: 0, usingMedkitTimeout: null
396
  };
397
 
398
  // Input
@@ -428,7 +450,10 @@
428
  window.addEventListener('keyup',(e)=>{
429
  const k = e.key.toLowerCase();
430
  if (k in keys) keys[k] = false;
431
- if (k === 'e') keys.e = true;
 
 
 
432
  if (k === 'q') keys.q = true;
433
  });
434
 
@@ -451,12 +476,10 @@
451
  });
452
  window.addEventListener('mouseup', ()=> mouse.down = false);
453
 
454
- // Touch support for mobile: update aim based on canvas touches (does not auto-shoot)
455
- // Also, we wire separate on-screen shoot button to control mouse.down.
456
  (function addCanvasTouchHandlers(){
457
  let canvasTouchId = null;
458
  canvas.addEventListener('touchstart', (ev) => {
459
- // find first touch that's inside canvas and not used by joystick/buttons
460
  for (const t of ev.changedTouches){
461
  const rect = canvas.getBoundingClientRect();
462
  const x = t.clientX - rect.left, y = t.clientY - rect.top;
@@ -667,10 +690,6 @@
667
  function updateHUD(){
668
  const now = performance.now();
669
  let healthText = `${Math.max(0,Math.floor(player.health))}%`;
670
- if (player.usingMedkit){
671
- const remaining = Math.max(0, Math.ceil((player.usingMedkitUntil - now)/1000));
672
- healthText += ` (Healing ${remaining}s)`;
673
- }
674
  hudHealthText.textContent = healthText;
675
  const slots = hudGear.querySelectorAll('.gear-slot');
676
  slots.forEach(s => {
@@ -751,63 +770,33 @@
751
  }
752
  }
753
 
754
- // Player interact & medkit usage
755
  function attemptUseOrInteract(){
756
  const sel = player.selectedSlot;
757
  const selItem = player.inventory[sel];
758
  if (selItem && selItem.type === 'medkit'){
759
- // toggle or start medkit usage
760
- if (player.usingMedkit){
761
- cancelPlayerMedkitUse();
762
- } else {
763
- startPlayerMedkitUse(sel);
764
- }
765
  } else {
766
  interactNearby();
767
  }
768
  }
769
 
770
- function startPlayerMedkitUse(slot){
771
  const it = player.inventory[slot];
772
  if (!it || it.type !== 'medkit' || it.amount <= 0) return;
773
- if (player.usingMedkit) return;
774
  const now = performance.now();
775
- player.usingMedkit = true;
776
- player.usingMedkitStart = now;
777
- player.usingMedkitUntil = now + 3000; // 3 seconds
778
- // set a timeout to apply heal
779
- player.usingMedkitTimeout = setTimeout(()=> {
780
- // ensure still using and item present
781
- if (!player.usingMedkit) return;
782
- const cur = player.inventory[slot];
783
- if (cur && cur.type === 'medkit'){
784
- cur.amount -= 1;
785
- player.health = Math.min(100, player.health + 50);
786
- if (cur.amount <= 0) player.inventory[slot] = null;
787
- }
788
- player.usingMedkit = false;
789
- player.usingMedkitTimeout = null;
790
- updateHUD();
791
- }, 3000);
792
- updateHUD();
793
- }
794
-
795
- function cancelPlayerMedkitUse(){
796
- if (!player.usingMedkit) return;
797
- player.usingMedkit = false;
798
- player.usingMedkitStart = 0;
799
- player.usingMedkitUntil = 0;
800
- if (player.usingMedkitTimeout){
801
- clearTimeout(player.usingMedkitTimeout);
802
- player.usingMedkitTimeout = null;
803
- }
804
  updateHUD();
805
  }
806
 
807
  function interactNearby(){
808
  const sel = player.selectedSlot;
809
  const selItem = player.inventory[sel];
810
- // medkit immediate use removed — now 3s usage handled above
811
  const range = 56;
812
  for (const chest of chests){
813
  if (chest.opened) continue;
@@ -865,7 +854,7 @@
865
  }
866
  }
867
 
868
- // Enemy helpers (medkit usage now takes 3s)
869
  function enemyEquipBestWeapon(e){
870
  let bestIdx = -1;
871
  let bestScore = -Infinity;
@@ -889,7 +878,6 @@
889
  e.usingMedkitStart = now;
890
  e.usingMedkitUntil = now + 3000;
891
  e.usingMedkitSlot = s;
892
- // set nextHealTime to prevent immediate re-trigger
893
  e.nextHealTime = now + 5000;
894
  return true;
895
  }
@@ -963,7 +951,6 @@
963
  const it = e.inventory[s];
964
  if (it && it.type==='medkit'){ it.amount += p.amount;
965
  if (e.health < 60) {
966
- // start medkit use if not already using
967
  if (!e.usingMedkit) enemyStartMedkitUse(e, performance.now());
968
  }
969
  return;
@@ -1059,7 +1046,7 @@
1059
  return chosen;
1060
  }
1061
 
1062
- // Bullets update (rewritten for consistent collision handling)
1063
  function bulletsUpdate(dt){
1064
  for (let i=bullets.length-1;i>=0;i--){
1065
  const b = bullets[i];
@@ -1074,8 +1061,6 @@
1074
  if (dPlayer < hitRadiusPlayer){
1075
  // apply damage
1076
  player.health -= b.dmg;
1077
- // cancel medkit usage if player is healing
1078
- if (player.usingMedkit) cancelPlayerMedkitUse();
1079
  if (player.health <= 0){ player.health = 0; playerDeath(); }
1080
  bullets.splice(i,1);
1081
  continue;
@@ -1093,7 +1078,6 @@
1093
  e.lastAttackedTime = performance.now();
1094
  // cancel enemy medkit if they are healing
1095
  if (e.usingMedkit){
1096
- // if they were using medkit, cancel it when hit
1097
  e.usingMedkit = false; e.usingMedkitSlot = -1; e.usingMedkitStart = 0; e.usingMedkitUntil = 0;
1098
  }
1099
  if (e.health <= 0){
@@ -1115,12 +1099,10 @@
1115
  if (obj.dead) continue;
1116
  const rr = getObjectRadius(obj);
1117
  const d = Math.hypot(obj.x - b.x, obj.y - b.y);
1118
- // use a small tolerance so bullets clearly collide
1119
  if (d < rr + 2){
1120
  obj.hp -= b.dmg;
1121
  if (obj.hp <= 0){
1122
  obj.dead = true;
1123
- // reward materials if player shot the object
1124
  if (b.shooter === 'player') player.materials += (obj.type === 'wood' ? 3 : 6);
1125
  }
1126
  hitObj = obj;
@@ -1132,7 +1114,7 @@
1132
  continue;
1133
  }
1134
 
1135
- // 4) Hit chests (if wanted bullets can open chests) — keep old behaviour
1136
  for (const chest of chests){
1137
  if (chest.opened) continue;
1138
  const d = Math.hypot(chest.x - b.x, chest.y - b.y);
@@ -1154,7 +1136,7 @@
1154
  }
1155
  }
1156
 
1157
- // Helpers to find nearest
1158
  function findNearestChest(e, maxDist = 1200){
1159
  let best = null; let bd = Infinity;
1160
  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; } }
@@ -1171,7 +1153,7 @@
1171
  return best;
1172
  }
1173
 
1174
- // Enemy AI (with medkit use delay)
1175
  function updateEnemies(dt, now){
1176
  const minSeparation = 20;
1177
  for (const e of enemies){
@@ -1199,18 +1181,16 @@
1199
 
1200
  // Handle enemy medkit usage finishing/cancelling
1201
  if (e.usingMedkit){
1202
- // if they were attacked while using, cancel
1203
  if (e.lastAttackedTime > e.usingMedkitStart){
1204
  e.usingMedkit = false; e.usingMedkitSlot = -1; e.usingMedkitStart = 0; e.usingMedkitUntil = 0;
1205
  } else if (now >= e.usingMedkitUntil){
1206
  applyEnemyMedkitUseNow(e);
1207
  } else {
1208
- // still using medkit; don't do other heavy actions
1209
  continue;
1210
  }
1211
  }
1212
 
1213
- // STORM behavior (unchanged)
1214
  if (storm.active){
1215
  const distToSafeCenter = Math.hypot(e.x - storm.centerX, e.y - storm.centerY);
1216
  if (distToSafeCenter > storm.radius){
@@ -1280,7 +1260,6 @@
1280
  if (e.state === 'gather') e.gatherTimeLeft -= dt;
1281
 
1282
  if (e.health < 60 && now >= (e.nextHealTime || 0) && !e.usingMedkit){
1283
- // start medkit use (3s) if possible
1284
  if (enemyStartMedkitUse(e, now)){
1285
  continue;
1286
  }
@@ -1365,7 +1344,7 @@
1365
  continue;
1366
  }
1367
 
1368
- // Combat logic remains (omitted here for brevity but preserved)
1369
  if (e.equippedIndex === -1) enemyEquipBestWeapon(e);
1370
  if (e.reloadPending){
1371
  if (now >= e.reloadingUntil){
@@ -1538,7 +1517,7 @@
1538
  }
1539
  }
1540
 
1541
- // Separation
1542
  for (let i = 0; i < enemies.length; i++){
1543
  const a = enemies[i];
1544
  if (!a || a.health <= 0) continue;
@@ -1586,7 +1565,6 @@
1586
  while (stormDamageAccumulator >= 1){
1587
  stormDamageAccumulator -=1;
1588
  player.health -= 1;
1589
- if (player.usingMedkit) cancelPlayerMedkitUse();
1590
  if (player.health <= 0){ player.health = 0; playerDeath(); }
1591
  }
1592
  } else stormDamageAccumulator = 0;
@@ -1611,8 +1589,7 @@
1611
  }
1612
  }
1613
 
1614
- // Drawing functions (unchanged, omitted here for brevity)...
1615
- // (All draw functions from the previous code remain unchanged and are present below.)
1616
  function drawWorld(){
1617
  const TILE = 600;
1618
  const cols = Math.ceil(WORLD.width / TILE);
@@ -1820,7 +1797,7 @@
1820
 
1821
  function drawCrosshair(){}
1822
 
1823
- // Minimap functions (unchanged)
1824
  function buildMiniTerrainCache(){
1825
  const mw = minimapCanvas.width;
1826
  const mh = minimapCanvas.height;
@@ -1921,6 +1898,7 @@
1921
  const dt = Math.min(0.05, (ts - lastTime)/1000);
1922
  lastTime = ts;
1923
 
 
1924
  let dx=0, dy=0;
1925
  if (keys.w) dy -= 1; if (keys.s) dy += 1; if (keys.a) dx -= 1; if (keys.d) dx += 1;
1926
  if (dx !== 0 || dy !== 0){
@@ -1940,8 +1918,7 @@
1940
  if (isCollidingSolid(player.x, player.y, player.radius)){
1941
  player.y = oldY;
1942
  }
1943
- // If player moves while using medkit, cancel use
1944
- if (player.usingMedkit && (dx !== 0 || dy !== 0)) cancelPlayerMedkitUse();
1945
  }
1946
  player.x = Math.max(16, Math.min(WORLD.width-16, player.x));
1947
  player.y = Math.max(16, Math.min(WORLD.height-16, player.y));
@@ -1973,7 +1950,6 @@
1973
  }
1974
 
1975
  if (keys.r) { reloadEquipped(); keys.r = false; }
1976
- if (keys.e){ attemptUseOrInteract(); keys.e = false; }
1977
  if (keys.q){ tryBuild(); keys.q = false; }
1978
 
1979
  updateEnemies(dt, performance.now());
@@ -2034,7 +2010,7 @@
2034
  player.health = 100; player.armor = 0; player.kills = 0; player.materials = 0;
2035
  player.inventory = [null,null,null,null,null];
2036
  player.selectedSlot = 0; player.equippedIndex = -1; player.lastShot = 0; player.lastMelee = 0;
2037
- player.usingMedkit = false; player.usingMedkitTimeout = null;
2038
 
2039
  populateWorld();
2040
  initHUD();
@@ -2082,8 +2058,6 @@
2082
  function endGame(){ gameActive = false; alert('Match over!'); }
2083
  function playerDeath(){
2084
  gameActive = false;
2085
- // cancel medkit use on death
2086
- cancelPlayerMedkitUse();
2087
  deathScreen.classList.remove('hidden');
2088
  }
2089
 
@@ -2157,7 +2131,7 @@
2157
  });
2158
  });
2159
 
2160
- // Mobile controls wiring (only show on mobile devices)
2161
  (function setupMobileControls(){
2162
  const joystick = document.getElementById('mobileJoystick');
2163
  const thumb = document.getElementById('joystickThumb');
@@ -2166,8 +2140,10 @@
2166
  const buildBtn = document.getElementById('buildBtn');
2167
  const reloadBtn = document.getElementById('reloadBtn');
2168
 
2169
- const isMobile = ('ontouchstart' in window) || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
2170
- if (!isMobile) {
 
 
2171
  mobileControls.classList.add('hidden');
2172
  if (mobileButtonsHud) mobileButtonsHud.classList.add('hidden');
2173
  return;
@@ -2176,7 +2152,7 @@
2176
  mobileControls.setAttribute('aria-hidden','false');
2177
  if (mobileButtonsHud) { mobileButtonsHud.classList.remove('hidden'); mobileButtonsHud.setAttribute('aria-hidden','false'); }
2178
 
2179
- // Joystick state
2180
  let joyTouchId = null;
2181
  let joyCenter = null;
2182
  const maxRadius = 52; // thumb allowed radius
@@ -2205,12 +2181,10 @@
2205
  ny = dy / dist * maxRadius;
2206
  }
2207
  thumb.style.transform = `translate(${nx}px, ${ny}px)`;
2208
- // map to directional keys
2209
  if (dist < deadzone){
2210
  keys.w = keys.a = keys.s = keys.d = false;
2211
  return;
2212
  }
2213
- // use axis threshold approach
2214
  keys.w = (dy < -10);
2215
  keys.s = (dy > 10);
2216
  keys.a = (dx < -10);
@@ -2223,10 +2197,8 @@
2223
  resetJoystick();
2224
  }
2225
 
2226
- // Touch handlers for joystick
2227
  joystick.addEventListener('touchstart', (ev) => {
2228
  for (const t of ev.changedTouches){
2229
- // only accept first active joystick touch
2230
  if (joyTouchId === null){
2231
  handleJoyStart(t);
2232
  ev.preventDefault();
@@ -2273,12 +2245,14 @@
2273
  if (shootTouchId === null){
2274
  shootTouchId = t.identifier;
2275
  mouse.down = true;
2276
- // set aim to center of canvas for quick shots if not touching canvas
2277
- const rect = canvas.getBoundingClientRect();
2278
- mouse.canvasX = rect.width * 0.6;
2279
- mouse.canvasY = rect.height * 0.5;
2280
- mouse.worldX = mouse.canvasX + camera.x;
2281
- mouse.worldY = mouse.canvasY + camera.y;
 
 
2282
  e.preventDefault();
2283
  break;
2284
  }
@@ -2335,6 +2309,100 @@
2335
  [shootBtn, interactBtn, buildBtn, reloadBtn].forEach(b => {
2336
  b.addEventListener('touchmove', (ev)=> ev.preventDefault(), { passive:false });
2337
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2338
  })();
2339
 
2340
  // Initialize UI state
 
3
  <head>
4
  <meta charset="utf-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>BattleZone Royale - Mobile Improvements</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <script src="https://unpkg.com/feather-icons"></script>
9
  <style>
 
137
  -webkit-user-select:none;
138
  user-select:none;
139
  }
140
+ .mobile-joystick.aim {
141
+ right: 12px;
142
+ left: auto;
143
+ }
144
+ .mobile-joystick.small {
145
+ width:110px; height:110px;
146
+ }
147
  .mobile-joystick .thumb {
148
  width:58px; height:58px; border-radius:50%;
149
  background: rgba(255,255,255,0.08);
 
151
  transform: translate(0,0);
152
  transition: transform 0.06s linear;
153
  }
154
+ .mobile-joystick.small .thumb { width:44px; height:44px; }
155
+
156
  /* Buttons now reside left of pickaxe slot in HUD; this container is shown only on mobile */
157
  .mobile-buttons-hud {
158
  display:flex;
 
182
  .mobile-controls { display:none !important; }
183
  .mobile-buttons-hud { display:none !important; } /* hide HUD buttons on desktop too */
184
  }
185
+
186
+ /* Make minimap smaller on narrow/mobile */
187
+ @media (max-width: 900px){
188
+ #minimap { width:120px; height:78px; right:8px; top:8px; padding:5px; }
189
+ }
190
  </style>
191
  </head>
192
  <body>
 
226
  <li><strong>WASD</strong> — Move</li>
227
  <li><strong>Mouse</strong> — Aim</li>
228
  <li><strong>Left Click</strong> — Shoot (if weapon equipped or in selected slot) or pickaxe melee</li>
229
+ <li><strong>E</strong> — Use medkit in selected slot OR Interact / Loot (press once)</li>
230
  <li><strong>Q</strong> — Build (costs 10 materials)</li>
231
  <li><strong>R</strong> — Reload selected weapon</li>
232
  <li><strong>1-5</strong> — Select inventory slot (also equips it)</li>
 
336
  <div id="hudGear" aria-label="gear"></div>
337
  </div>
338
 
339
+ <!-- Mobile Controls: left joystick (movement) and right joystick (aim) -->
340
  <div id="mobileControls" class="mobile-controls hidden" aria-hidden="true">
341
  <div id="mobileJoystick" class="mobile-joystick" role="application" aria-label="Movement joystick">
342
  <div id="joystickThumb" class="thumb"></div>
343
  </div>
344
+ <div id="mobileJoystickAim" class="mobile-joystick aim small" role="application" aria-label="Aiming joystick">
345
+ <div id="joystickThumbAim" class="thumb"></div>
346
+ </div>
347
  </div>
348
 
349
  </div>
 
351
  </div>
352
 
353
  <script>
354
+ // Global mobile detection
355
+ const IS_MOBILE = ('ontouchstart' in window) || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
356
+
357
  // DOM references
358
  const landingScreen = document.getElementById('landingScreen');
359
  const gameScreen = document.getElementById('gameScreen');
 
369
  const victoryScreen = document.getElementById('victoryScreen');
370
  const goHomeBtn = document.getElementById('goHomeBtn');
371
  const continueBtn = document.getElementById('continueBtn');
 
372
 
373
  // Mobile button references (in HUD)
374
  const mobileButtonsHud = document.getElementById('mobileButtonsHud');
 
375
  const mobileControls = document.getElementById('mobileControls');
376
 
377
  // Loading screen elements
 
393
  const ctn = document.getElementById('canvasContainer');
394
  canvas.width = Math.max(900, Math.floor(ctn.clientWidth));
395
  canvas.height = Math.max(560, Math.floor(ctn.clientHeight));
396
+ // make minimap smaller on mobile
397
+ if (IS_MOBILE){
398
+ minimapCanvas.width = 120;
399
+ minimapCanvas.height = 78;
400
+ } else {
401
+ minimapCanvas.width = 220;
402
+ minimapCanvas.height = 140;
403
+ }
404
  cameraUpdate();
405
  miniTerrainCache = null; // rebuild on resize
406
  }
 
414
  selectedSlot:0,
415
  equippedIndex: -1,
416
  lastShot:0, lastMelee:0,
417
+ lastMedkitUsed: 0 // cooldown tracker for instant medkit presses
 
418
  };
419
 
420
  // Input
 
450
  window.addEventListener('keyup',(e)=>{
451
  const k = e.key.toLowerCase();
452
  if (k in keys) keys[k] = false;
453
+ // Press E once to use medkit or interact (no holding)
454
+ if (k === 'e') {
455
+ attemptUseOrInteract();
456
+ }
457
  if (k === 'q') keys.q = true;
458
  });
459
 
 
476
  });
477
  window.addEventListener('mouseup', ()=> mouse.down = false);
478
 
479
+ // Touch support for canvas aiming (still supported)
 
480
  (function addCanvasTouchHandlers(){
481
  let canvasTouchId = null;
482
  canvas.addEventListener('touchstart', (ev) => {
 
483
  for (const t of ev.changedTouches){
484
  const rect = canvas.getBoundingClientRect();
485
  const x = t.clientX - rect.left, y = t.clientY - rect.top;
 
690
  function updateHUD(){
691
  const now = performance.now();
692
  let healthText = `${Math.max(0,Math.floor(player.health))}%`;
 
 
 
 
693
  hudHealthText.textContent = healthText;
694
  const slots = hudGear.querySelectorAll('.gear-slot');
695
  slots.forEach(s => {
 
770
  }
771
  }
772
 
773
+ // Player interact & medkit usage - instantaneous on press, usable while moving/taking damage
774
  function attemptUseOrInteract(){
775
  const sel = player.selectedSlot;
776
  const selItem = player.inventory[sel];
777
  if (selItem && selItem.type === 'medkit'){
778
+ useMedkitInstant(sel);
 
 
 
 
 
779
  } else {
780
  interactNearby();
781
  }
782
  }
783
 
784
+ function useMedkitInstant(slot){
785
  const it = player.inventory[slot];
786
  if (!it || it.type !== 'medkit' || it.amount <= 0) return;
 
787
  const now = performance.now();
788
+ // small cooldown to avoid spamming
789
+ if (player.lastMedkitUsed && now - player.lastMedkitUsed < 700) return;
790
+ player.lastMedkitUsed = now;
791
+ it.amount -= 1;
792
+ player.health = Math.min(100, player.health + 50);
793
+ if (it.amount <= 0) player.inventory[slot] = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
794
  updateHUD();
795
  }
796
 
797
  function interactNearby(){
798
  const sel = player.selectedSlot;
799
  const selItem = player.inventory[sel];
 
800
  const range = 56;
801
  for (const chest of chests){
802
  if (chest.opened) continue;
 
854
  }
855
  }
856
 
857
+ // Enemy medkit logic kept unchanged (they still take 3s)
858
  function enemyEquipBestWeapon(e){
859
  let bestIdx = -1;
860
  let bestScore = -Infinity;
 
878
  e.usingMedkitStart = now;
879
  e.usingMedkitUntil = now + 3000;
880
  e.usingMedkitSlot = s;
 
881
  e.nextHealTime = now + 5000;
882
  return true;
883
  }
 
951
  const it = e.inventory[s];
952
  if (it && it.type==='medkit'){ it.amount += p.amount;
953
  if (e.health < 60) {
 
954
  if (!e.usingMedkit) enemyStartMedkitUse(e, performance.now());
955
  }
956
  return;
 
1046
  return chosen;
1047
  }
1048
 
1049
+ // Bullets update (keep same but removed medkit cancellation when taking damage)
1050
  function bulletsUpdate(dt){
1051
  for (let i=bullets.length-1;i>=0;i--){
1052
  const b = bullets[i];
 
1061
  if (dPlayer < hitRadiusPlayer){
1062
  // apply damage
1063
  player.health -= b.dmg;
 
 
1064
  if (player.health <= 0){ player.health = 0; playerDeath(); }
1065
  bullets.splice(i,1);
1066
  continue;
 
1078
  e.lastAttackedTime = performance.now();
1079
  // cancel enemy medkit if they are healing
1080
  if (e.usingMedkit){
 
1081
  e.usingMedkit = false; e.usingMedkitSlot = -1; e.usingMedkitStart = 0; e.usingMedkitUntil = 0;
1082
  }
1083
  if (e.health <= 0){
 
1099
  if (obj.dead) continue;
1100
  const rr = getObjectRadius(obj);
1101
  const d = Math.hypot(obj.x - b.x, obj.y - b.y);
 
1102
  if (d < rr + 2){
1103
  obj.hp -= b.dmg;
1104
  if (obj.hp <= 0){
1105
  obj.dead = true;
 
1106
  if (b.shooter === 'player') player.materials += (obj.type === 'wood' ? 3 : 6);
1107
  }
1108
  hitObj = obj;
 
1114
  continue;
1115
  }
1116
 
1117
+ // 4) Hit chests
1118
  for (const chest of chests){
1119
  if (chest.opened) continue;
1120
  const d = Math.hypot(chest.x - b.x, chest.y - b.y);
 
1136
  }
1137
  }
1138
 
1139
+ // Helpers to find nearest (unchanged)
1140
  function findNearestChest(e, maxDist = 1200){
1141
  let best = null; let bd = Infinity;
1142
  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; } }
 
1153
  return best;
1154
  }
1155
 
1156
+ // Enemy AI (keeps behavior; medkit usage for enemies remains as 3s)
1157
  function updateEnemies(dt, now){
1158
  const minSeparation = 20;
1159
  for (const e of enemies){
 
1181
 
1182
  // Handle enemy medkit usage finishing/cancelling
1183
  if (e.usingMedkit){
 
1184
  if (e.lastAttackedTime > e.usingMedkitStart){
1185
  e.usingMedkit = false; e.usingMedkitSlot = -1; e.usingMedkitStart = 0; e.usingMedkitUntil = 0;
1186
  } else if (now >= e.usingMedkitUntil){
1187
  applyEnemyMedkitUseNow(e);
1188
  } else {
 
1189
  continue;
1190
  }
1191
  }
1192
 
1193
+ // STORM behavior
1194
  if (storm.active){
1195
  const distToSafeCenter = Math.hypot(e.x - storm.centerX, e.y - storm.centerY);
1196
  if (distToSafeCenter > storm.radius){
 
1260
  if (e.state === 'gather') e.gatherTimeLeft -= dt;
1261
 
1262
  if (e.health < 60 && now >= (e.nextHealTime || 0) && !e.usingMedkit){
 
1263
  if (enemyStartMedkitUse(e, now)){
1264
  continue;
1265
  }
 
1344
  continue;
1345
  }
1346
 
1347
+ // Combat logic ...
1348
  if (e.equippedIndex === -1) enemyEquipBestWeapon(e);
1349
  if (e.reloadPending){
1350
  if (now >= e.reloadingUntil){
 
1517
  }
1518
  }
1519
 
1520
+ // Separation and clipping
1521
  for (let i = 0; i < enemies.length; i++){
1522
  const a = enemies[i];
1523
  if (!a || a.health <= 0) continue;
 
1565
  while (stormDamageAccumulator >= 1){
1566
  stormDamageAccumulator -=1;
1567
  player.health -= 1;
 
1568
  if (player.health <= 0){ player.health = 0; playerDeath(); }
1569
  }
1570
  } else stormDamageAccumulator = 0;
 
1589
  }
1590
  }
1591
 
1592
+ // Drawing functions (kept)
 
1593
  function drawWorld(){
1594
  const TILE = 600;
1595
  const cols = Math.ceil(WORLD.width / TILE);
 
1797
 
1798
  function drawCrosshair(){}
1799
 
1800
+ // Minimap functions
1801
  function buildMiniTerrainCache(){
1802
  const mw = minimapCanvas.width;
1803
  const mh = minimapCanvas.height;
 
1898
  const dt = Math.min(0.05, (ts - lastTime)/1000);
1899
  lastTime = ts;
1900
 
1901
+ // Movement from keys/joystick
1902
  let dx=0, dy=0;
1903
  if (keys.w) dy -= 1; if (keys.s) dy += 1; if (keys.a) dx -= 1; if (keys.d) dx += 1;
1904
  if (dx !== 0 || dy !== 0){
 
1918
  if (isCollidingSolid(player.x, player.y, player.radius)){
1919
  player.y = oldY;
1920
  }
1921
+ // Note: medkit use is instant now; moving does not cancel medkit
 
1922
  }
1923
  player.x = Math.max(16, Math.min(WORLD.width-16, player.x));
1924
  player.y = Math.max(16, Math.min(WORLD.height-16, player.y));
 
1950
  }
1951
 
1952
  if (keys.r) { reloadEquipped(); keys.r = false; }
 
1953
  if (keys.q){ tryBuild(); keys.q = false; }
1954
 
1955
  updateEnemies(dt, performance.now());
 
2010
  player.health = 100; player.armor = 0; player.kills = 0; player.materials = 0;
2011
  player.inventory = [null,null,null,null,null];
2012
  player.selectedSlot = 0; player.equippedIndex = -1; player.lastShot = 0; player.lastMelee = 0;
2013
+ player.lastMedkitUsed = 0;
2014
 
2015
  populateWorld();
2016
  initHUD();
 
2058
  function endGame(){ gameActive = false; alert('Match over!'); }
2059
  function playerDeath(){
2060
  gameActive = false;
 
 
2061
  deathScreen.classList.remove('hidden');
2062
  }
2063
 
 
2131
  });
2132
  });
2133
 
2134
+ // Mobile controls wiring: movement joystick + aim joystick + HUD buttons
2135
  (function setupMobileControls(){
2136
  const joystick = document.getElementById('mobileJoystick');
2137
  const thumb = document.getElementById('joystickThumb');
 
2140
  const buildBtn = document.getElementById('buildBtn');
2141
  const reloadBtn = document.getElementById('reloadBtn');
2142
 
2143
+ const aimJoystick = document.getElementById('mobileJoystickAim');
2144
+ const aimThumb = document.getElementById('joystickThumbAim');
2145
+
2146
+ if (!IS_MOBILE) {
2147
  mobileControls.classList.add('hidden');
2148
  if (mobileButtonsHud) mobileButtonsHud.classList.add('hidden');
2149
  return;
 
2152
  mobileControls.setAttribute('aria-hidden','false');
2153
  if (mobileButtonsHud) { mobileButtonsHud.classList.remove('hidden'); mobileButtonsHud.setAttribute('aria-hidden','false'); }
2154
 
2155
+ // Movement joystick
2156
  let joyTouchId = null;
2157
  let joyCenter = null;
2158
  const maxRadius = 52; // thumb allowed radius
 
2181
  ny = dy / dist * maxRadius;
2182
  }
2183
  thumb.style.transform = `translate(${nx}px, ${ny}px)`;
 
2184
  if (dist < deadzone){
2185
  keys.w = keys.a = keys.s = keys.d = false;
2186
  return;
2187
  }
 
2188
  keys.w = (dy < -10);
2189
  keys.s = (dy > 10);
2190
  keys.a = (dx < -10);
 
2197
  resetJoystick();
2198
  }
2199
 
 
2200
  joystick.addEventListener('touchstart', (ev) => {
2201
  for (const t of ev.changedTouches){
 
2202
  if (joyTouchId === null){
2203
  handleJoyStart(t);
2204
  ev.preventDefault();
 
2245
  if (shootTouchId === null){
2246
  shootTouchId = t.identifier;
2247
  mouse.down = true;
2248
+ // if aim joystick not used, aim forward in facing direction (so shooting has a direction)
2249
+ if (!aimActive) {
2250
+ const rect = canvas.getBoundingClientRect();
2251
+ mouse.canvasX = rect.width * 0.6;
2252
+ mouse.canvasY = rect.height * 0.5;
2253
+ mouse.worldX = mouse.canvasX + camera.x;
2254
+ mouse.worldY = mouse.canvasY + camera.y;
2255
+ }
2256
  e.preventDefault();
2257
  break;
2258
  }
 
2309
  [shootBtn, interactBtn, buildBtn, reloadBtn].forEach(b => {
2310
  b.addEventListener('touchmove', (ev)=> ev.preventDefault(), { passive:false });
2311
  });
2312
+
2313
+ // Aim joystick (right)
2314
+ let aimTouchId = null;
2315
+ let aimCenter = null;
2316
+ const aimMaxRadius = 44; // smaller control
2317
+ let aimActive = false;
2318
+
2319
+ function resetAim(){
2320
+ aimThumb.style.transform = `translate(0px,0px)`;
2321
+ aimActive = false;
2322
+ }
2323
+
2324
+ function aimStart(t){
2325
+ const rect = aimJoystick.getBoundingClientRect();
2326
+ aimCenter = { x: rect.left + rect.width/2, y: rect.top + rect.height/2 };
2327
+ aimTouchId = t.identifier;
2328
+ aimActive = true;
2329
+ updateAim(t.clientX, t.clientY);
2330
+ }
2331
+
2332
+ function updateAim(clientX, clientY){
2333
+ if (!aimCenter) return;
2334
+ let dx = clientX - aimCenter.x;
2335
+ let dy = clientY - aimCenter.y;
2336
+ const dist = Math.hypot(dx, dy);
2337
+ let nx = dx, ny = dy;
2338
+ if (dist > aimMaxRadius){
2339
+ nx = dx / dist * aimMaxRadius;
2340
+ ny = dy / dist * aimMaxRadius;
2341
+ }
2342
+ aimThumb.style.transform = `translate(${nx}px, ${ny}px)`;
2343
+
2344
+ // compute angle and set player's aim
2345
+ if (Math.hypot(nx, ny) < 6) return;
2346
+ const angle = Math.atan2(ny, nx); // aim direction relative to screen; convert to world
2347
+ // Map aim direction to a point in world in front of player (distance scaled)
2348
+ const aimDistance = 600; // how far the aim projects
2349
+ const targetWorldX = player.x + Math.cos(angle) * aimDistance;
2350
+ const targetWorldY = player.y + Math.sin(angle) * aimDistance;
2351
+
2352
+ mouse.worldX = targetWorldX;
2353
+ mouse.worldY = targetWorldY;
2354
+ // update canvas coords based on camera
2355
+ mouse.canvasX = mouse.worldX - camera.x;
2356
+ mouse.canvasY = mouse.worldY - camera.y;
2357
+ player.angle = Math.atan2(mouse.worldY - player.y, mouse.worldX - player.x);
2358
+ }
2359
+
2360
+ function aimEnd(){
2361
+ aimTouchId = null;
2362
+ aimCenter = null;
2363
+ resetAim();
2364
+ aimActive = false;
2365
+ }
2366
+
2367
+ aimJoystick.addEventListener('touchstart', (ev) => {
2368
+ for (const t of ev.changedTouches){
2369
+ if (aimTouchId === null){
2370
+ aimStart(t);
2371
+ ev.preventDefault();
2372
+ break;
2373
+ }
2374
+ }
2375
+ }, { passive:false });
2376
+
2377
+ window.addEventListener('touchmove', (ev) => {
2378
+ for (const t of ev.changedTouches){
2379
+ if (t.identifier === aimTouchId){
2380
+ updateAim(t.clientX, t.clientY);
2381
+ ev.preventDefault();
2382
+ break;
2383
+ }
2384
+ }
2385
+ }, { passive:false });
2386
+
2387
+ window.addEventListener('touchend', (ev) => {
2388
+ for (const t of ev.changedTouches){
2389
+ if (t.identifier === aimTouchId){
2390
+ aimEnd();
2391
+ ev.preventDefault();
2392
+ break;
2393
+ }
2394
+ }
2395
+ });
2396
+
2397
+ window.addEventListener('touchcancel', (ev) => {
2398
+ for (const t of ev.changedTouches){
2399
+ if (t.identifier === aimTouchId){
2400
+ aimEnd();
2401
+ ev.preventDefault();
2402
+ break;
2403
+ }
2404
+ }
2405
+ });
2406
  })();
2407
 
2408
  // Initialize UI state