bbc123321 commited on
Commit
0e2313f
·
verified ·
1 Parent(s): 4a18fa9

Manual changes saved

Browse files
Files changed (1) hide show
  1. index.html +81 -97
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 - Player Sprite & Bullet Collisions</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <script src="https://unpkg.com/feather-icons"></script>
9
  <style>
@@ -190,18 +190,6 @@
190
  lastShot:0, lastMelee:0
191
  };
192
 
193
- // Load player sprite from provided URL
194
- const playerSprite = new Image();
195
- playerSprite.crossOrigin = "anonymous";
196
- playerSprite.src = "https://www.vhv.rs/dpng/d/17-179278_fortnite-default-skin-blonde-hair-hd-png-download.png";
197
- let playerSpriteLoaded = false;
198
- playerSprite.onload = () => playerSpriteLoaded = true;
199
- playerSprite.onerror = () => {
200
- // if CORS blocks or image fails, we keep triangle fallback
201
- console.warn('Player sprite failed to load — triangle fallback will be used.');
202
- playerSpriteLoaded = false;
203
- };
204
-
205
  // Input
206
  const keys = { w:false,a:false,s:false,d:false,e:false,q:false,r:false,f:false };
207
  const mouse = { canvasX:0, canvasY:0, worldX:0, worldY:0, down:false };
@@ -344,6 +332,7 @@
344
  }
345
 
346
  // Populate world
 
347
  function populateWorld(){
348
  chests.length = 0; objects.length = 0; enemies.length = 0; pickups.length = 0;
349
 
@@ -376,6 +365,7 @@
376
  const ex = rand(300, WORLD.width-300);
377
  const ey = rand(300, WORLD.height-300);
378
 
 
379
  const enemy = {
380
  id:'e'+i, x:ex, y:ey, radius:14, angle:rand(0,Math.PI*2), speed:110+rand(-20,20),
381
  health: 80 + randInt(0,40), lastMelee:0, meleeRate:800 + randInt(-200,200),
@@ -398,17 +388,12 @@
398
  prioritizeChestsUntil: 0
399
  };
400
 
401
- const w = enemyWeapons[randInt(0, enemyWeapons.length)];
402
- const proto = makeWeaponProto(w);
403
- enemy.inventory[0] = {
404
- type: 'weapon',
405
- weapon: proto,
406
- ammoInMag: proto.magSize,
407
- ammoReserve: proto.startReserve
408
- };
409
- enemy.equippedIndex = 0;
410
- enemy.prioritizeChestsUntil = now + 4000 + rand(0,2000);
411
- enemy.gatherTimeLeft = Math.min(enemy.gatherTimeLeft, 6);
412
 
413
  enemies.push(enemy);
414
  }
@@ -473,7 +458,7 @@
473
  }
474
  function worldToScreen(wx,wy){ return { x: Math.round(wx - camera.x), y: Math.round(wy - camera.y) }; }
475
 
476
- // Combat utilities
477
  function shootBullet(originX, originY, targetX, targetY, weaponObj, shooterId){
478
  if (!weaponObj || (typeof weaponObj.ammoInMag === 'number' && weaponObj.ammoInMag <= 0)) return false;
479
  const speed = 1100;
@@ -636,6 +621,7 @@
636
  return;
637
  }
638
  }
 
639
  let worstIdx = -1, worstScore = Infinity;
640
  for (let s=0;s<5;s++){
641
  const it = e.inventory[s];
@@ -730,31 +716,33 @@
730
  else if (blocker.type === 'stone') br = 22;
731
  else if (blocker.type === 'wood') br = 16;
732
  else br = 18;
 
733
  const vx = fromX - blocker.x;
734
  const vy = fromY - blocker.y;
735
  const len = Math.hypot(vx, vy) || 0.0001;
 
736
  const px = -vy / len;
737
  const py = vx / len;
738
  const radius = br + padding + 8;
739
  const cand1 = { x: blocker.x + px * radius, y: blocker.y + py * radius };
740
  const cand2 = { x: blocker.x - px * radius, y: blocker.y - py * radius };
 
741
  const d1 = Math.hypot(cand1.x - goalX, cand1.y - goalY);
742
  const d2 = Math.hypot(cand2.x - goalX, cand2.y - goalY);
743
  const chosen = d1 < d2 ? cand1 : cand2;
 
744
  chosen.x = Math.max(16, Math.min(WORLD.width-16, chosen.x));
745
  chosen.y = Math.max(16, Math.min(WORLD.height-16, chosen.y));
746
  return chosen;
747
  }
748
 
749
- // Bullets update: now bullets hit and damage harvestable terrain (wood/stone/wall)
750
  function bulletsUpdate(dt){
751
  for (let i=bullets.length-1;i>=0;i--){
752
  const b = bullets[i];
753
  b.x += b.vx * dt; b.y += b.vy * dt;
754
  b.traveled += Math.hypot(b.vx*dt, b.vy*dt);
755
  b.life -= dt;
756
-
757
- // Hit player
758
  if (b.dmg > 0 && b.shooter !== 'player'){
759
  if (Math.hypot(player.x - b.x, player.y - b.y) < 16){
760
  player.health -= b.dmg;
@@ -762,10 +750,7 @@
762
  bullets.splice(i,1); continue;
763
  }
764
  }
765
-
766
- // Hit enemies
767
  if (b.dmg > 0){
768
- let hitSomething = false;
769
  for (const e of enemies){
770
  if (e.health <= 0) continue;
771
  if (b.shooter === e.id) continue;
@@ -773,50 +758,34 @@
773
  e.health -= b.dmg;
774
  e.lastAttackedTime = performance.now();
775
  if (e.health <= 0){ e.health = 0; if (b.shooter === 'player'){ player.kills++; player.materials += 2; updatePlayerCount(); } }
776
- bullets.splice(i,1); hitSomething = true; break;
777
  }
778
  }
779
- if (hitSomething) continue;
780
  }
781
-
782
- // Hit world objects (now includes wood/stone/wall) - bullets stop when hitting these
783
- let hitObject = false;
784
- for (const obj of objects){
785
- if (obj.dead) continue;
786
- const rr = getObjectRadius(obj);
787
- if (Math.hypot(obj.x - b.x, obj.y - b.y) < rr){
788
- // Damage the object
789
- // Stone could be more resistant - scaling factor
790
- const factor = (obj.type === 'stone') ? 0.8 : 1.0;
791
- obj.hp -= b.dmg * factor;
792
- if (obj.hp <= 0 && !obj.dead){
793
- obj.dead = true;
794
- // reward player for destroying object if they shot it
795
- if (b.shooter === 'player') player.materials += (obj.type === 'wood' ? 3 : 6);
796
  }
797
- bullets.splice(i,1);
798
- hitObject = true;
799
- break;
800
  }
801
- }
802
- if (hitObject) continue;
803
-
804
- // Hit chests (unchanged)
805
- for (const chest of chests){
806
- if (!chest.opened && Math.hypot(chest.x - b.x, chest.y - b.y) < 18){
807
- chest.opened = true;
808
- const loot = chest.loot;
809
- const px = chest.x + rand(-20,20), py = chest.y + rand(-20,20);
810
- if (loot.type === 'weapon') pickups.push({ x:px, y:py, type:'weapon', weapon: makeWeaponProto(loot.weapon), ammoInMag: loot.weapon.magSize || 12, ammoReserve: loot.weapon.startReserve || (loot.weapon.magSize*2 || 24) });
811
- else if (loot.type === 'medkit') pickups.push({ x:px, y:py, type:'medkit', amount: loot.amount || 1 });
812
- else if (loot.type === 'materials') pickups.push({ x:px, y:py, type:'materials', amount: loot.amount || 10 });
813
- bullets.splice(i,1);
814
- hitObject = true;
815
- break;
816
  }
 
817
  }
818
- if (hitObject) continue;
819
-
820
  if (b.life <= 0 || b.traveled > 2600) bullets.splice(i,1);
821
  }
822
  }
@@ -838,18 +807,20 @@
838
  return best;
839
  }
840
 
841
- // Enemy AI (kept mostly same)
842
  function updateEnemies(dt, now){
843
  const minSeparation = 20;
844
  for (const e of enemies){
845
  if (e.health <= 0) continue;
846
  if (!e.spawnSafeUntil) e.spawnSafeUntil = performance.now() + SPAWN_PROTECT_MS;
847
 
 
848
  if (storm.active){
849
  const distToSafeCenter = Math.hypot(e.x - storm.centerX, e.y - storm.centerY);
850
  if (distToSafeCenter > storm.radius){
851
  e.state = 'toSafe';
852
 
 
853
  if (e.tempTarget && now < (e.tempTargetExpiry || 0)){
854
  const td = Math.hypot(e.tempTarget.x - e.x, e.tempTarget.y - e.y);
855
  if (td > 8){
@@ -862,17 +833,21 @@
862
  }
863
  }
864
 
 
865
  if (hasLineOfSight(e.x, e.y, storm.centerX, storm.centerY)){
866
  e.angle = Math.atan2(storm.centerY - e.y, storm.centerX - e.x);
867
  moveEntityWithCollision(e, Math.cos(e.angle) * e.speed * dt * 1.0, Math.sin(e.angle) * e.speed * dt * 1.0, e.radius);
868
  continue;
869
  }
870
 
 
871
  const blocker = findBlockingObject(e.x, e.y, storm.centerX, storm.centerY);
872
  if (blocker){
873
  const db = Math.hypot(blocker.x - e.x, blocker.y - e.y);
874
 
 
875
  if (db < 48){
 
876
  if (e.equippedIndex >= 0){
877
  const eq = e.inventory[e.equippedIndex];
878
  if (eq && eq.type === 'weapon' && eq.ammoInMag > 0 && now - (e.lastShot||0) > (eq.weapon.rate || 300)){
@@ -880,22 +855,26 @@
880
  eq.ammoInMag -= 1;
881
  const angle = Math.atan2(blocker.y - e.y, blocker.x - e.x);
882
  shootBullet(e.x + Math.cos(angle)*12, e.y + Math.sin(angle)*12, blocker.x + (Math.random()-0.5)*6, blocker.y + (Math.random()-0.5)*6, eq, e.id);
883
- e.angle = angle + (Math.random()-0.5)*0.6;
884
- moveEntityWithCollision(e, Math.cos(e.angle) * e.speed * dt * 0.35, Math.sin(e.angle) * e.speed * dt * 0.35, e.radius);
 
885
  continue;
886
  }
887
  }
 
888
  blocker.hp -= 28 * dt;
889
  if (blocker.hp <= 0){
890
  blocker.dead = true;
891
  e.materials += (blocker.type === 'wood' ? 3 : 6);
892
  } else {
 
893
  e.angle = Math.atan2(blocker.y - e.y, blocker.x - e.x);
894
  moveEntityWithCollision(e, Math.cos(e.angle) * e.speed * dt * 0.4, Math.sin(e.angle) * e.speed * dt * 0.4, e.radius);
895
  }
896
  continue;
897
  }
898
 
 
899
  const waypoint = computeDetourWaypoint(e.x, e.y, blocker, storm.centerX, storm.centerY, 12);
900
  if (waypoint){
901
  e.tempTarget = waypoint;
@@ -911,17 +890,20 @@
911
  }
912
  }
913
 
 
914
  e.angle = Math.atan2(blocker.y - e.y, blocker.x - e.x);
915
  moveEntityWithCollision(e, Math.cos(e.angle) * e.speed * dt * 0.6, Math.sin(e.angle) * e.speed * dt * 0.6, e.radius);
916
  continue;
917
  }
918
 
 
919
  e.angle = Math.atan2(storm.centerY - e.y, storm.centerX - e.x);
920
  moveEntityWithCollision(e, Math.cos(e.angle) * e.speed * dt * 0.95, Math.sin(e.angle) * e.speed * dt * 0.95, e.radius);
921
  continue;
922
  }
923
  }
924
 
 
925
  if (now - e.lastAttackedTime < 4000) e.state = 'combat';
926
  if (e.state === 'gather') e.gatherTimeLeft -= dt;
927
 
@@ -1265,7 +1247,7 @@
1265
  }
1266
  }
1267
 
1268
- // Drawing world & entities
1269
  function drawWorld(){
1270
  const TILE = 600;
1271
  const cols = Math.ceil(WORLD.width / TILE);
@@ -1416,39 +1398,32 @@
1416
  }
1417
  }
1418
 
1419
- // draw player using loaded sprite; fallback to triangle if sprite unavailable
1420
  function drawPlayer(){
1421
  const s = worldToScreen(player.x,player.y);
1422
  ctx.save();
1423
- ctx.translate(s.x,s.y);
1424
- ctx.rotate(player.angle);
1425
-
1426
- // shadow
1427
- ctx.fillStyle='rgba(0,0,0,0.25)'; ctx.beginPath(); ctx.ellipse(0,18,22,8,0,0,Math.PI*2); ctx.fill();
1428
-
1429
- if (playerSpriteLoaded){
1430
- // draw sprite centered; scale to fit nicely
1431
- const w = 48;
1432
- const h = 64;
1433
- // optionally flip the sprite so it faces the mouse direction if the image faces right by default.
1434
- // The provided image faces forward; rotation should be sufficient.
1435
- ctx.drawImage(playerSprite, -w/2, -h/2, w, h);
1436
- } else {
1437
- // fallback triangle
1438
- ctx.fillStyle='yellow';
1439
- ctx.beginPath(); ctx.moveTo(18,0); ctx.lineTo(-12,-10); ctx.lineTo(-12,10); ctx.closePath(); ctx.fill();
1440
- }
1441
 
1442
- // draw equipped weapon overlay if any
1443
  let activeWeaponItem = null;
1444
  if (player.equippedIndex >= 0) activeWeaponItem = player.inventory[player.equippedIndex];
1445
  else {
1446
  const selected = player.inventory[player.selectedSlot];
1447
  if (selected && selected.type === 'weapon') activeWeaponItem = selected;
1448
  }
1449
- if (activeWeaponItem && activeWeaponItem.type === 'weapon'){
 
1450
  ctx.save();
1451
- ctx.translate(14, 6);
 
 
 
 
 
 
 
 
 
1452
  ctx.rotate(-0.05);
1453
  ctx.fillStyle = activeWeaponItem.weapon.color || '#fff';
1454
  ctx.fillRect(0,-4,26,8);
@@ -1456,12 +1431,11 @@
1456
  ctx.fillRect(18,-2,8,4);
1457
  const magPct = Math.max(0, activeWeaponItem.ammoInMag / activeWeaponItem.weapon.magSize);
1458
  ctx.fillStyle = 'rgba(0,0,0,0.35)';
1459
- ctx.fillRect(4,8,18,4);
1460
  ctx.fillStyle = '#0f0';
1461
- ctx.fillRect(4,8,18*magPct,4);
1462
  ctx.restore();
1463
  }
1464
-
1465
  ctx.restore();
1466
  }
1467
 
@@ -1498,8 +1472,10 @@
1498
  const mw = minimapCanvas.width;
1499
  const mh = minimapCanvas.height;
1500
  if (!miniTerrainCache) buildMiniTerrainCache();
 
1501
  miniCtx.putImageData(miniTerrainCache, 0, 0);
1502
 
 
1503
  miniCtx.save();
1504
  const scaleX = mw / WORLD.width;
1505
  const scaleY = mh / WORLD.height;
@@ -1518,6 +1494,7 @@
1518
  miniCtx.fillRect(px-2, py-2, 4, 4);
1519
  }
1520
  }
 
1521
  for (const p of pickups){
1522
  const px = Math.round(p.x * scaleX);
1523
  const py = Math.round(p.y * scaleY);
@@ -1532,17 +1509,22 @@
1532
  }
1533
  }
1534
 
 
1535
  if (storm.active){
 
1536
  miniCtx.fillStyle = 'rgba(0,0,0,0.35)';
1537
  miniCtx.fillRect(0,0,mw,mh);
 
1538
  miniCtx.globalCompositeOperation = 'destination-out';
1539
  const cx = storm.centerX * scaleX;
1540
  const cy = storm.centerY * scaleY;
 
1541
  const r = storm.radius * ((scaleX + scaleY) / 2);
1542
  miniCtx.beginPath();
1543
  miniCtx.arc(cx, cy, r, 0, Math.PI*2);
1544
  miniCtx.fill();
1545
  miniCtx.globalCompositeOperation = 'source-over';
 
1546
  miniCtx.strokeStyle = 'rgba(255,200,80,0.95)';
1547
  miniCtx.lineWidth = 2;
1548
  miniCtx.beginPath();
@@ -1550,6 +1532,7 @@
1550
  miniCtx.stroke();
1551
  }
1552
 
 
1553
  const ppx = Math.round(player.x * (mw / WORLD.width));
1554
  const ppy = Math.round(player.y * (mh / WORLD.height));
1555
  miniCtx.fillStyle = '#ffff66';
@@ -1638,6 +1621,7 @@
1638
  drawWorld(); drawObjects(); drawChests(); drawPickups(); drawEnemies(); drawBullets(); drawPlayer(); drawCrosshair();
1639
  updateHUD();
1640
 
 
1641
  drawMinimap();
1642
 
1643
  if (storm.active && playerInStorm()) stormWarning.classList.remove('hidden'); else stormWarning.classList.add('hidden');
 
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>
 
190
  lastShot:0, lastMelee:0
191
  };
192
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  // Input
194
  const keys = { w:false,a:false,s:false,d:false,e:false,q:false,r:false,f:false };
195
  const mouse = { canvasX:0, canvasY:0, worldX:0, worldY:0, down:false };
 
332
  }
333
 
334
  // Populate world
335
+ // Updated: enemies spawn already equipped with a weapon in inventory slot 0 (equipped)
336
  function populateWorld(){
337
  chests.length = 0; objects.length = 0; enemies.length = 0; pickups.length = 0;
338
 
 
365
  const ex = rand(300, WORLD.width-300);
366
  const ey = rand(300, WORLD.height-300);
367
 
368
+ // create enemy base
369
  const enemy = {
370
  id:'e'+i, x:ex, y:ey, radius:14, angle:rand(0,Math.PI*2), speed:110+rand(-20,20),
371
  health: 80 + randInt(0,40), lastMelee:0, meleeRate:800 + randInt(-200,200),
 
388
  prioritizeChestsUntil: 0
389
  };
390
 
391
+
392
+ enemy.equippedIndex = 0; // equip the weapon immediately
393
+ // Slightly bias them to seek chests early so they can refill/reload if needed
394
+ enemy.prioritizeChestsUntil = now + 10000 + rand(0,2000);
395
+ // Reduce gather time so they don't idle unnecessarily
396
+ enemy.gatherTimeLeft = Math.min(enemy.gatherTimeLeft, 10);
 
 
 
 
 
397
 
398
  enemies.push(enemy);
399
  }
 
458
  }
459
  function worldToScreen(wx,wy){ return { x: Math.round(wx - camera.x), y: Math.round(wy - camera.y) }; }
460
 
461
+ // Combat utilities (kept same as before)
462
  function shootBullet(originX, originY, targetX, targetY, weaponObj, shooterId){
463
  if (!weaponObj || (typeof weaponObj.ammoInMag === 'number' && weaponObj.ammoInMag <= 0)) return false;
464
  const speed = 1100;
 
621
  return;
622
  }
623
  }
624
+ // replace worst if pickup is better
625
  let worstIdx = -1, worstScore = Infinity;
626
  for (let s=0;s<5;s++){
627
  const it = e.inventory[s];
 
716
  else if (blocker.type === 'stone') br = 22;
717
  else if (blocker.type === 'wood') br = 16;
718
  else br = 18;
719
+ // vector from blocker to from
720
  const vx = fromX - blocker.x;
721
  const vy = fromY - blocker.y;
722
  const len = Math.hypot(vx, vy) || 0.0001;
723
+ // perp directions
724
  const px = -vy / len;
725
  const py = vx / len;
726
  const radius = br + padding + 8;
727
  const cand1 = { x: blocker.x + px * radius, y: blocker.y + py * radius };
728
  const cand2 = { x: blocker.x - px * radius, y: blocker.y - py * radius };
729
+ // choose candidate that's closer to goal and not inside other solids (approx)
730
  const d1 = Math.hypot(cand1.x - goalX, cand1.y - goalY);
731
  const d2 = Math.hypot(cand2.x - goalX, cand2.y - goalY);
732
  const chosen = d1 < d2 ? cand1 : cand2;
733
+ // clamp to world
734
  chosen.x = Math.max(16, Math.min(WORLD.width-16, chosen.x));
735
  chosen.y = Math.max(16, Math.min(WORLD.height-16, chosen.y));
736
  return chosen;
737
  }
738
 
739
+ // Bullets update
740
  function bulletsUpdate(dt){
741
  for (let i=bullets.length-1;i>=0;i--){
742
  const b = bullets[i];
743
  b.x += b.vx * dt; b.y += b.vy * dt;
744
  b.traveled += Math.hypot(b.vx*dt, b.vy*dt);
745
  b.life -= dt;
 
 
746
  if (b.dmg > 0 && b.shooter !== 'player'){
747
  if (Math.hypot(player.x - b.x, player.y - b.y) < 16){
748
  player.health -= b.dmg;
 
750
  bullets.splice(i,1); continue;
751
  }
752
  }
 
 
753
  if (b.dmg > 0){
 
754
  for (const e of enemies){
755
  if (e.health <= 0) continue;
756
  if (b.shooter === e.id) continue;
 
758
  e.health -= b.dmg;
759
  e.lastAttackedTime = performance.now();
760
  if (e.health <= 0){ e.health = 0; if (b.shooter === 'player'){ player.kills++; player.materials += 2; updatePlayerCount(); } }
761
+ bullets.splice(i,1); break;
762
  }
763
  }
764
+ if (!bullets[i]) continue;
765
  }
766
+ if (!b.tracer && b.dmg > 0){
767
+ for (const obj of objects){
768
+ if (obj.dead) continue;
769
+ if (obj.type === 'wall' && Math.hypot(obj.x - b.x, obj.y - b.y) < 18){
770
+ obj.hp -= b.dmg;
771
+ if (obj.hp <= 0 && !obj.dead){ obj.dead = true; if (b.shooter === 'player') player.materials += 6; }
772
+ bullets.splice(i,1); break;
 
 
 
 
 
 
 
 
773
  }
 
 
 
774
  }
775
+ if (!bullets[i]) continue;
776
+ for (const chest of chests){
777
+ if (!chest.opened && Math.hypot(chest.x - b.x, chest.y - b.y) < 18){
778
+ chest.opened = true;
779
+ const loot = chest.loot;
780
+ const px = chest.x + rand(-20,20), py = chest.y + rand(-20,20);
781
+ if (loot.type === 'weapon') pickups.push({ x:px, y:py, type:'weapon', weapon: makeWeaponProto(loot.weapon), ammoInMag: loot.weapon.magSize || 12, ammoReserve: loot.weapon.startReserve || (loot.weapon.magSize*2 || 24) });
782
+ else if (loot.type === 'medkit') pickups.push({ x:px, y:py, type:'medkit', amount: loot.amount || 1 });
783
+ else if (loot.type === 'materials') pickups.push({ x:px, y:py, type:'materials', amount: loot.amount || 10 });
784
+ bullets.splice(i,1); break;
785
+ }
 
 
 
 
786
  }
787
+ if (!bullets[i]) continue;
788
  }
 
 
789
  if (b.life <= 0 || b.traveled > 2600) bullets.splice(i,1);
790
  }
791
  }
 
807
  return best;
808
  }
809
 
810
+ // Enemy AI (improved: detour around obstacles and break obstructions to reach safe zone)
811
  function updateEnemies(dt, now){
812
  const minSeparation = 20;
813
  for (const e of enemies){
814
  if (e.health <= 0) continue;
815
  if (!e.spawnSafeUntil) e.spawnSafeUntil = performance.now() + SPAWN_PROTECT_MS;
816
 
817
+ // === STORM / MOVE TO SAFE ZONE BEHAVIOR ===
818
  if (storm.active){
819
  const distToSafeCenter = Math.hypot(e.x - storm.centerX, e.y - storm.centerY);
820
  if (distToSafeCenter > storm.radius){
821
  e.state = 'toSafe';
822
 
823
+ // If the enemy already has a temporary waypoint, move toward it until expiry
824
  if (e.tempTarget && now < (e.tempTargetExpiry || 0)){
825
  const td = Math.hypot(e.tempTarget.x - e.x, e.tempTarget.y - e.y);
826
  if (td > 8){
 
833
  }
834
  }
835
 
836
+ // Direct sight to safe center -> run straight
837
  if (hasLineOfSight(e.x, e.y, storm.centerX, storm.centerY)){
838
  e.angle = Math.atan2(storm.centerY - e.y, storm.centerX - e.x);
839
  moveEntityWithCollision(e, Math.cos(e.angle) * e.speed * dt * 1.0, Math.sin(e.angle) * e.speed * dt * 1.0, e.radius);
840
  continue;
841
  }
842
 
843
+ // If no direct sight, find the blocking object
844
  const blocker = findBlockingObject(e.x, e.y, storm.centerX, storm.centerY);
845
  if (blocker){
846
  const db = Math.hypot(blocker.x - e.x, blocker.y - e.y);
847
 
848
+ // If very close to blocker -> attempt to break it physically (melee) or shoot it if has ammo
849
  if (db < 48){
850
+ // If has a weapon with ammo, shoot at blocker
851
  if (e.equippedIndex >= 0){
852
  const eq = e.inventory[e.equippedIndex];
853
  if (eq && eq.type === 'weapon' && eq.ammoInMag > 0 && now - (e.lastShot||0) > (eq.weapon.rate || 300)){
 
855
  eq.ammoInMag -= 1;
856
  const angle = Math.atan2(blocker.y - e.y, blocker.x - e.x);
857
  shootBullet(e.x + Math.cos(angle)*12, e.y + Math.sin(angle)*12, blocker.x + (Math.random()-0.5)*6, blocker.y + (Math.random()-0.5)*6, eq, e.id);
858
+ // small nudge away/around while shooting
859
+ e.angle = angle + (Math.random()-0.10)*0.10;
860
+ moveEntityWithCollision(e, Math.cos(e.angle) * e.speed * dt * 0.50, Math.sin(e.angle) * e.speed * dt * 0.50, e.radius);
861
  continue;
862
  }
863
  }
864
+ // No ammo or no weapon -> use melee to whack the blocker
865
  blocker.hp -= 28 * dt;
866
  if (blocker.hp <= 0){
867
  blocker.dead = true;
868
  e.materials += (blocker.type === 'wood' ? 3 : 6);
869
  } else {
870
+ // move closer and keep hitting
871
  e.angle = Math.atan2(blocker.y - e.y, blocker.x - e.x);
872
  moveEntityWithCollision(e, Math.cos(e.angle) * e.speed * dt * 0.4, Math.sin(e.angle) * e.speed * dt * 0.4, e.radius);
873
  }
874
  continue;
875
  }
876
 
877
+ // If current blocker is not adjacent, compute a detour waypoint around it
878
  const waypoint = computeDetourWaypoint(e.x, e.y, blocker, storm.centerX, storm.centerY, 12);
879
  if (waypoint){
880
  e.tempTarget = waypoint;
 
890
  }
891
  }
892
 
893
+ // Fallback: try to path straight toward blocker to push through or destroy it
894
  e.angle = Math.atan2(blocker.y - e.y, blocker.x - e.x);
895
  moveEntityWithCollision(e, Math.cos(e.angle) * e.speed * dt * 0.6, Math.sin(e.angle) * e.speed * dt * 0.6, e.radius);
896
  continue;
897
  }
898
 
899
+ // Default: move toward safe center (should not reach here often)
900
  e.angle = Math.atan2(storm.centerY - e.y, storm.centerX - e.x);
901
  moveEntityWithCollision(e, Math.cos(e.angle) * e.speed * dt * 0.95, Math.sin(e.angle) * e.speed * dt * 0.95, e.radius);
902
  continue;
903
  }
904
  }
905
 
906
+ // === NORMAL BEHAVIOR (unchanged) ===
907
  if (now - e.lastAttackedTime < 4000) e.state = 'combat';
908
  if (e.state === 'gather') e.gatherTimeLeft -= dt;
909
 
 
1247
  }
1248
  }
1249
 
1250
+ // Drawing world & entities (unchanged)
1251
  function drawWorld(){
1252
  const TILE = 600;
1253
  const cols = Math.ceil(WORLD.width / TILE);
 
1398
  }
1399
  }
1400
 
 
1401
  function drawPlayer(){
1402
  const s = worldToScreen(player.x,player.y);
1403
  ctx.save();
1404
+ ctx.translate(s.x,s.y); ctx.rotate(player.angle);
1405
+ ctx.fillStyle='rgba(0,0,0,0.25)'; ctx.beginPath(); ctx.ellipse(0,14,18,8,0,0,Math.PI*2); ctx.fill();
1406
+ ctx.fillStyle='yellow'; ctx.beginPath(); ctx.moveTo(18,0); ctx.lineTo(-12,-10); ctx.lineTo(-12,10); ctx.closePath(); ctx.fill();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1407
 
 
1408
  let activeWeaponItem = null;
1409
  if (player.equippedIndex >= 0) activeWeaponItem = player.inventory[player.equippedIndex];
1410
  else {
1411
  const selected = player.inventory[player.selectedSlot];
1412
  if (selected && selected.type === 'weapon') activeWeaponItem = selected;
1413
  }
1414
+
1415
+ if (player.equippedIndex === -1){
1416
  ctx.save();
1417
+ ctx.translate(12, 6);
1418
+ ctx.rotate(-0.2);
1419
+ ctx.fillStyle = '#8b6b4a';
1420
+ ctx.fillRect(-2,0,4,18);
1421
+ ctx.fillStyle = '#cfcfcf';
1422
+ ctx.fillRect(-8,-6,12,6);
1423
+ ctx.restore();
1424
+ } else if (activeWeaponItem && activeWeaponItem.type === 'weapon'){
1425
+ ctx.save();
1426
+ ctx.translate(12, 2);
1427
  ctx.rotate(-0.05);
1428
  ctx.fillStyle = activeWeaponItem.weapon.color || '#fff';
1429
  ctx.fillRect(0,-4,26,8);
 
1431
  ctx.fillRect(18,-2,8,4);
1432
  const magPct = Math.max(0, activeWeaponItem.ammoInMag / activeWeaponItem.weapon.magSize);
1433
  ctx.fillStyle = 'rgba(0,0,0,0.35)';
1434
+ ctx.fillRect(4,6,18,4);
1435
  ctx.fillStyle = '#0f0';
1436
+ ctx.fillRect(4,6,18*magPct,4);
1437
  ctx.restore();
1438
  }
 
1439
  ctx.restore();
1440
  }
1441
 
 
1472
  const mw = minimapCanvas.width;
1473
  const mh = minimapCanvas.height;
1474
  if (!miniTerrainCache) buildMiniTerrainCache();
1475
+ // draw base terrain
1476
  miniCtx.putImageData(miniTerrainCache, 0, 0);
1477
 
1478
+ // draw world objects (trees/stones/walls) as tiny marks - chests excluded per request
1479
  miniCtx.save();
1480
  const scaleX = mw / WORLD.width;
1481
  const scaleY = mh / WORLD.height;
 
1494
  miniCtx.fillRect(px-2, py-2, 4, 4);
1495
  }
1496
  }
1497
+ // optionally show pickups (not enemies/chests) - small blue/green dots
1498
  for (const p of pickups){
1499
  const px = Math.round(p.x * scaleX);
1500
  const py = Math.round(p.y * scaleY);
 
1509
  }
1510
  }
1511
 
1512
+ // Storm safe zone: draw overlay darkening outside safe zone and stroke the safe circle
1513
  if (storm.active){
1514
+ // darken everything
1515
  miniCtx.fillStyle = 'rgba(0,0,0,0.35)';
1516
  miniCtx.fillRect(0,0,mw,mh);
1517
+ // carve out safe zone
1518
  miniCtx.globalCompositeOperation = 'destination-out';
1519
  const cx = storm.centerX * scaleX;
1520
  const cy = storm.centerY * scaleY;
1521
+ // radius scaled by average axis to keep circle shape close
1522
  const r = storm.radius * ((scaleX + scaleY) / 2);
1523
  miniCtx.beginPath();
1524
  miniCtx.arc(cx, cy, r, 0, Math.PI*2);
1525
  miniCtx.fill();
1526
  miniCtx.globalCompositeOperation = 'source-over';
1527
+ // border
1528
  miniCtx.strokeStyle = 'rgba(255,200,80,0.95)';
1529
  miniCtx.lineWidth = 2;
1530
  miniCtx.beginPath();
 
1532
  miniCtx.stroke();
1533
  }
1534
 
1535
+ // draw player dot (always visible)
1536
  const ppx = Math.round(player.x * (mw / WORLD.width));
1537
  const ppy = Math.round(player.y * (mh / WORLD.height));
1538
  miniCtx.fillStyle = '#ffff66';
 
1621
  drawWorld(); drawObjects(); drawChests(); drawPickups(); drawEnemies(); drawBullets(); drawPlayer(); drawCrosshair();
1622
  updateHUD();
1623
 
1624
+ // Draw the minimap last so it reflects the latest world state
1625
  drawMinimap();
1626
 
1627
  if (storm.active && playerInStorm()) stormWarning.classList.remove('hidden'); else stormWarning.classList.add('hidden');