zeroed-some/cob / 2a6657e

Browse files

improve divebomb, notification spacing

Authored by espadonne
SHA
2a6657e74ecfd661f247a56f3a4f0fef46a52451
Parents
9a2a133
Tree
d249f5c

2 changed files

StatusFile+-
M js/entities.js 416 394
M js/game.js 44 29
js/entities.jsmodified
@@ -513,78 +513,78 @@ class Fly {
513513
     }
514514
   }
515515
 
516
-    checkWebCollisions() {
517
-    let currentlyTouching = new Set();
516
+  checkWebCollisions () {
517
+    let currentlyTouching = new Set()
518518
 
519519
     for (let strand of webStrands) {
520
-      let touching = false;
520
+      let touching = false
521521
 
522522
       // Check collision with strand path
523523
       if (strand.path && strand.path.length > 1) {
524524
         // OPTIMIZATION: Skip every other point for collision detection
525525
         for (let i = 0; i < strand.path.length - 1; i += 2) {
526
-          let p1 = strand.path[i];
527
-          let p2 = strand.path[Math.min(i + 1, strand.path.length - 1)];
528
-          let d = this.pointToLineDistance(this.pos, p1, p2);
526
+          let p1 = strand.path[i]
527
+          let p2 = strand.path[Math.min(i + 1, strand.path.length - 1)]
528
+          let d = this.pointToLineDistance(this.pos, p1, p2)
529529
           if (d < this.radius + 3) {
530
-            touching = true;
531
-            break;
530
+            touching = true
531
+            break
532532
           }
533533
         }
534534
       } else if (strand.start && strand.end) {
535535
         // Fallback for strands without path
536
-        let d = this.pointToLineDistance(this.pos, strand.start, strand.end);
536
+        let d = this.pointToLineDistance(this.pos, strand.start, strand.end)
537537
         if (d < this.radius + 3) {
538
-          touching = true;
538
+          touching = true
539539
         }
540540
       }
541541
 
542542
       if (touching) {
543
-        currentlyTouching.add(strand);
543
+        currentlyTouching.add(strand)
544544
 
545545
         // If this is a new strand we're touching
546546
         if (!this.touchedStrands.has(strand)) {
547
-          this.touchedStrands.add(strand);
547
+          this.touchedStrands.add(strand)
548548
 
549549
           // Vibrate the web when first touching
550
-          strand.vibrate(3);
550
+          strand.vibrate(3)
551551
 
552552
           // First strand slows us down
553553
           if (this.touchedStrands.size === 1) {
554
-            this.currentSpeed = this.baseSpeed * 0.4; // Slow to 40% speed
555
-            this.slowedBy.add(strand);
554
+            this.currentSpeed = this.baseSpeed * 0.4 // Slow to 40% speed
555
+            this.slowedBy.add(strand)
556556
 
557557
             // Visual feedback - yellow particles for slowing
558558
             // LIMIT PARTICLES TO PREVENT FREEZE
559
-            let particleCount = Math.min(3, 100 - particles.length);
559
+            let particleCount = Math.min(3, 100 - particles.length)
560560
             for (let j = 0; j < particleCount; j++) {
561
-              let p = new Particle(this.pos.x, this.pos.y);
562
-              p.color = color(255, 255, 0, 150);
563
-              p.vel = createVector(random(-1, 1), random(-1, 1));
564
-              p.size = 3;
565
-              particles.push(p);
561
+              let p = new Particle(this.pos.x, this.pos.y)
562
+              p.color = color(255, 255, 0, 150)
563
+              p.vel = createVector(random(-1, 1), random(-1, 1))
564
+              p.size = 3
565
+              particles.push(p)
566566
             }
567567
           }
568568
           // Second strand catches us
569569
           else if (this.touchedStrands.size >= 2 && !this.caught) {
570
-            this.caught = true;
571
-            this.currentSpeed = 0;
570
+            this.caught = true
571
+            this.currentSpeed = 0
572572
 
573573
             // Stronger vibration when caught
574
-            strand.vibrate(8);
574
+            strand.vibrate(8)
575575
 
576576
             // FIX: OPTIMIZE NEARBY STRAND VIBRATION
577577
             // This is likely the main cause of the freeze - checking distances between all strands
578578
             // Use a more efficient method
579
-            propagateVibration(strand, 2);
579
+            propagateVibration(strand, 2)
580580
 
581581
             // Create caught particles - LIMIT TO PREVENT FREEZE
582
-            let particleCount = Math.min(6, 100 - particles.length);
582
+            let particleCount = Math.min(6, 100 - particles.length)
583583
             for (let j = 0; j < particleCount; j++) {
584
-              let p = new Particle(this.pos.x, this.pos.y);
585
-              p.color = color(255, 200, 0, 200);
586
-              p.vel = createVector(random(-2, 2), random(-2, 2));
587
-              particles.push(p);
584
+              let p = new Particle(this.pos.x, this.pos.y)
585
+              p.color = color(255, 200, 0, 200)
586
+              p.vel = createVector(random(-2, 2), random(-2, 2))
587
+              particles.push(p)
588588
             }
589589
           }
590590
         }
@@ -593,32 +593,34 @@ class Fly {
593593
 
594594
     // If we're no longer touching strands we were slowed by, speed back up
595595
     if (this.slowedBy.size > 0 && currentlyTouching.size === 0) {
596
-      this.currentSpeed = this.baseSpeed;
597
-      this.slowedBy.clear();
596
+      this.currentSpeed = this.baseSpeed
597
+      this.slowedBy.clear()
598598
     }
599599
   }
600600
 
601
-  pointToLineDistance(point, lineStart, lineEnd) {
601
+  pointToLineDistance (point, lineStart, lineEnd) {
602602
     // Add null checks
603
-    if (!point || !lineStart || !lineEnd) return Infinity;
604
-    
605
-    let dx = lineEnd.x - lineStart.x;
606
-    let dy = lineEnd.y - lineStart.y;
607
-    let lineLength = sqrt(dx * dx + dy * dy);
608
-    
603
+    if (!point || !lineStart || !lineEnd) return Infinity
604
+
605
+    let dx = lineEnd.x - lineStart.x
606
+    let dy = lineEnd.y - lineStart.y
607
+    let lineLength = sqrt(dx * dx + dy * dy)
608
+
609609
     // If line has no length, return distance to point
610610
     if (lineLength < 0.01) {
611
-      return dist(point.x, point.y, lineStart.x, lineStart.y);
611
+      return dist(point.x, point.y, lineStart.x, lineStart.y)
612612
     }
613
-    
613
+
614614
     // Use optimized calculation without creating new vectors
615
-    let t = ((point.x - lineStart.x) * dx + (point.y - lineStart.y) * dy) / (lineLength * lineLength);
616
-    t = constrain(t, 0, 1);
617
-    
618
-    let closestX = lineStart.x + t * dx;
619
-    let closestY = lineStart.y + t * dy;
620
-    
621
-    return dist(point.x, point.y, closestX, closestY);
615
+    let t =
616
+      ((point.x - lineStart.x) * dx + (point.y - lineStart.y) * dy) /
617
+      (lineLength * lineLength)
618
+    t = constrain(t, 0, 1)
619
+
620
+    let closestX = lineStart.x + t * dx
621
+    let closestY = lineStart.y + t * dy
622
+
623
+    return dist(point.x, point.y, closestX, closestY)
622624
   }
623625
 
624626
   display () {
@@ -1374,379 +1376,399 @@ class FoodBox {
13741376
 }
13751377
 
13761378
 class Bird {
1377
-    constructor(pattern, isThief = false) {
1378
-        this.pattern = pattern; // 'dive', 'swoop', 'glide', 'circle'
1379
-        this.isThief = isThief;
1380
-        this.active = false;
1381
-        this.attacking = false;
1382
-        this.attackDelay = isThief ? 120 : random(30, 90); // MUCH shorter initial delay
1383
-        
1384
-        // Position and movement
1385
-        this.x = random(width);
1386
-        this.y = -50; // Start above screen
1387
-        this.vx = 0;
1388
-        this.vy = 0;
1389
-        this.targetX = 0;
1390
-        this.targetY = 0;
1391
-        this.speed = 5; // Increased from 3
1392
-        this.angle = 0;
1393
-        this.wingPhase = random(TWO_PI);
1394
-        
1395
-        // Visual properties
1396
-        this.size = isThief ? 25 : 20;
1397
-        this.color = isThief ? color(100, 50, 150) : color(50, 50, 50);
1398
-        
1399
-        // Pattern-specific properties
1400
-        if (pattern === 'circle') {
1401
-            this.circleRadius = 150;
1402
-            this.circleAngle = 0;
1403
-            this.circleCenter = createVector(width/2, height/2);
1404
-        }
1405
-        
1406
-        // Attack properties - MUCH MORE AGGRESSIVE
1407
-        this.diveSpeed = 12; // Increased from 8
1408
-        this.retreatSpeed = 6; // Increased from 4
1409
-        this.state = 'waiting'; // 'waiting', 'approaching', 'attacking', 'retreating'
1410
-        this.consecutiveAttacks = 0; // Track multiple attacks
1411
-        this.maxConsecutiveAttacks = random(2, 4); // Each bird does 2-4 attacks before retreating
1379
+  constructor (pattern, isThief = false) {
1380
+    this.pattern = pattern // 'dive', 'swoop', 'glide', 'circle'
1381
+    this.isThief = isThief
1382
+    this.active = false
1383
+    this.attacking = false
1384
+    this.attackDelay = isThief ? 120 : random(30, 90) // MUCH shorter initial delay
1385
+
1386
+    // Position and movement
1387
+    this.x = random(width)
1388
+    this.y = -50 // Start above screen
1389
+    this.vx = 0
1390
+    this.vy = 0
1391
+    this.targetX = 0
1392
+    this.targetY = 0
1393
+    this.speed = 5 // Increased from 3
1394
+    this.angle = 0
1395
+    this.wingPhase = random(TWO_PI)
1396
+
1397
+    // Visual properties
1398
+    this.size = isThief ? 25 : 20
1399
+    this.color = isThief ? color(100, 50, 150) : color(50, 50, 50)
1400
+
1401
+    // Pattern-specific properties
1402
+    if (pattern === 'circle') {
1403
+      this.circleRadius = 150
1404
+      this.circleAngle = 0
1405
+      this.circleCenter = createVector(width / 2, height / 2)
14121406
     }
1413
-    
1414
-    update() {
1415
-        // Update wing animation
1416
-        this.wingPhase += 0.3; // Faster wing flapping
1417
-        
1418
-        // Countdown to attack - MUCH FASTER
1419
-        if (this.attackDelay > 0) {
1420
-            this.attackDelay--;
1421
-            // Hover while waiting - more aggressive hovering
1422
-            this.y = -30 + sin(frameCount * 0.08) * 15;
1423
-            this.x += sin(frameCount * 0.05) * 3;
1424
-            
1425
-            // Show warning when about to attack
1426
-            if (this.attackDelay < 30) {
1427
-                this.y = lerp(this.y, 50, 0.1); // Start moving into view
1428
-            }
1429
-            return;
1430
-        }
1431
-        
1432
-        // Activate after delay
1433
-        if (!this.active) {
1434
-            this.active = true;
1435
-            this.state = 'approaching';
1436
-            this.updateTarget(); // Set initial target
1437
-        }
1438
-        
1439
-        // Execute movement pattern
1440
-        switch(this.pattern) {
1441
-            case 'dive':
1442
-                this.executeDivePattern();
1443
-                break;
1444
-            case 'swoop':
1445
-                this.executeSwoopPattern();
1446
-                break;
1447
-            case 'glide':
1448
-                this.executeGlidePattern();
1449
-                break;
1450
-            case 'circle':
1451
-                this.executeCirclePattern();
1452
-                break;
1453
-        }
1454
-        
1455
-        // Check collisions
1456
-        this.checkCollisions();
1457
-        
1458
-        // Keep on screen during approach
1459
-        if (this.state === 'approaching') {
1460
-            this.x = constrain(this.x, 20, width - 20);
1407
+
1408
+    // Attack properties - MUCH MORE AGGRESSIVE
1409
+    this.diveSpeed = 12 // Increased from 8
1410
+    this.retreatSpeed = 6 // Increased from 4
1411
+    this.state = 'waiting' // 'waiting', 'approaching', 'attacking', 'retreating'
1412
+    this.consecutiveAttacks = 0 // Track multiple attacks
1413
+    this.maxConsecutiveAttacks = random(2, 4) // Each bird does 2-4 attacks before retreating
1414
+  }
1415
+
1416
+  update () {
1417
+    // Update wing animation
1418
+    this.wingPhase += 0.3 // Faster wing flapping
1419
+
1420
+    // Countdown to attack - MUCH FASTER
1421
+    if (this.attackDelay > 0) {
1422
+      this.attackDelay--
1423
+      // Hover while waiting - more aggressive hovering
1424
+      this.y = -30 + sin(frameCount * 0.08) * 15
1425
+      this.x += sin(frameCount * 0.05) * 3
1426
+
1427
+      // Show warning when about to attack
1428
+      if (this.attackDelay < 30) {
1429
+        this.y = lerp(this.y, 50, 0.1) // Start moving into view
1430
+      }
1431
+      return
1432
+    }
1433
+
1434
+    // Activate after delay
1435
+    if (!this.active) {
1436
+      this.active = true
1437
+      this.state = 'approaching'
1438
+      this.updateTarget() // Set initial target
1439
+    }
1440
+
1441
+    // Execute movement pattern
1442
+    switch (this.pattern) {
1443
+      case 'dive':
1444
+        this.executeDivePattern()
1445
+        break
1446
+      case 'swoop':
1447
+        this.executeSwoopPattern()
1448
+        break
1449
+      case 'glide':
1450
+        this.executeGlidePattern()
1451
+        break
1452
+      case 'circle':
1453
+        this.executeCirclePattern()
1454
+        break
1455
+    }
1456
+
1457
+    // Check collisions
1458
+    this.checkCollisions()
1459
+
1460
+    // Keep on screen during approach
1461
+    if (this.state === 'approaching') {
1462
+      this.x = constrain(this.x, 20, width - 20)
1463
+    }
1464
+  }
1465
+
1466
+  updateTarget () {
1467
+    if (this.isThief) {
1468
+      // Target caught flies
1469
+      let caughtFlies = flies.filter(f => f.stuck || f.caught)
1470
+      if (caughtFlies.length > 0) {
1471
+        let target = random(caughtFlies)
1472
+        this.targetX = target.pos.x
1473
+        this.targetY = target.pos.y
1474
+      } else {
1475
+        this.active = false // No targets, deactivate
1476
+        return
1477
+      }
1478
+    } else {
1479
+      // Heavily favor targeting spider (90% chance)
1480
+      if (random() < 0.9) {
1481
+        // Target spider with prediction
1482
+        this.targetX = spider.pos.x + spider.vel.x * 10 // Predict where spider will be
1483
+        this.targetY = spider.pos.y + spider.vel.y * 10
1484
+      } else {
1485
+        // Occasionally target a web strand
1486
+        if (webStrands.length > 0) {
1487
+          let strand = random(webStrands.filter(s => !s.broken))
1488
+          if (strand && strand.path && strand.path.length > 0) {
1489
+            let point = random(strand.path)
1490
+            this.targetX = point.x
1491
+            this.targetY = point.y
1492
+          }
14611493
         }
1494
+      }
14621495
     }
1463
-    
1464
-    updateTarget() {
1465
-        if (this.isThief) {
1466
-            // Target caught flies
1467
-            let caughtFlies = flies.filter(f => f.stuck || f.caught);
1468
-            if (caughtFlies.length > 0) {
1469
-                let target = random(caughtFlies);
1470
-                this.targetX = target.pos.x;
1471
-                this.targetY = target.pos.y;
1472
-            } else {
1473
-                this.active = false; // No targets, deactivate
1474
-                return;
1475
-            }
1496
+  }
1497
+
1498
+  executeDivePattern () {
1499
+    if (this.state === 'approaching') {
1500
+      // Move into position above target MUCH FASTER
1501
+      let dx = this.targetX - this.x
1502
+      let dy = 50 - this.y // Lower starting position for faster attack
1503
+
1504
+      this.x += dx * 0.15 // Much faster positioning
1505
+      this.y += dy * 0.15
1506
+
1507
+      // When in position, start diving immediately
1508
+      if (abs(dx) < 50 && abs(dy) < 30) {
1509
+        this.state = 'attacking'
1510
+        this.attacking = true
1511
+        this.updateTarget() // Update target position for more accurate dive
1512
+      }
1513
+    } else if (this.state === 'attacking') {
1514
+      // IMPROVED: Better tracking dive
1515
+      let dx = this.targetX - this.x
1516
+      let dy = this.targetY - this.y
1517
+
1518
+      // Accelerate toward target with better tracking
1519
+      this.vx = dx * 0.08 // Increased horizontal tracking
1520
+      this.vy = min(this.diveSpeed, this.vy + 1) // Accelerating dive
1521
+
1522
+      this.x += this.vx
1523
+      this.y += this.vy
1524
+
1525
+      // Update target position while diving for better accuracy
1526
+      if (frameCount % 10 === 0) {
1527
+        this.targetX = spider.pos.x
1528
+        this.targetY = spider.pos.y
1529
+      }
1530
+
1531
+      // FIX: Check if we've reached or passed the target
1532
+      // More generous hit detection
1533
+      if (this.y > this.targetY - 10 || this.y > height - 50) {
1534
+        this.consecutiveAttacks++
1535
+
1536
+        // Do multiple attacks before retreating
1537
+        if (this.consecutiveAttacks < this.maxConsecutiveAttacks) {
1538
+          // Quick pull up and attack again
1539
+          this.state = 'approaching'
1540
+          this.attacking = false
1541
+          this.y = min(this.y, height - 100) // Don't go too low
1542
+          this.updateTarget() // Get new target position
14761543
         } else {
1477
-            // Heavily favor targeting spider (90% chance)
1478
-            if (random() < 0.9) {
1479
-                // Target spider with prediction
1480
-                this.targetX = spider.pos.x + spider.vel.x * 10; // Predict where spider will be
1481
-                this.targetY = spider.pos.y + spider.vel.y * 10;
1482
-            } else {
1483
-                // Occasionally target a web strand
1484
-                if (webStrands.length > 0) {
1485
-                    let strand = random(webStrands.filter(s => !s.broken));
1486
-                    if (strand && strand.path && strand.path.length > 0) {
1487
-                        let point = random(strand.path);
1488
-                        this.targetX = point.x;
1489
-                        this.targetY = point.y;
1490
-                    }
1491
-                }
1492
-            }
1544
+          // Finally retreat after multiple attacks
1545
+          this.state = 'retreating'
1546
+          this.attacking = false
14931547
         }
1548
+      }
1549
+    } else if (this.state === 'retreating') {
1550
+      // Fly back up faster
1551
+      this.vy = -this.retreatSpeed
1552
+      this.y += this.vy
1553
+      this.x += sin(frameCount * 0.1) * 2 // Weave while retreating
1554
+
1555
+      // Reset when off screen
1556
+      if (this.y < -50) {
1557
+        this.state = 'approaching'
1558
+        this.attackDelay = random(60, 120) // Shorter delay between attack runs
1559
+        this.x = random(width)
1560
+        this.consecutiveAttacks = 0 // Reset attack counter
1561
+        this.maxConsecutiveAttacks = random(2, 4) // Randomize next attack count
1562
+      }
14941563
     }
1495
-    
1496
-    executeDivePattern() {
1497
-        if (this.state === 'approaching') {
1498
-            // Move into position above target MUCH FASTER
1499
-            let dx = this.targetX - this.x;
1500
-            let dy = 50 - this.y; // Lower starting position for faster attack
1501
-            
1502
-            this.x += dx * 0.15; // Much faster positioning
1503
-            this.y += dy * 0.15;
1504
-            
1505
-            // When in position, start diving immediately
1506
-            if (abs(dx) < 50 && abs(dy) < 30) {
1507
-                this.state = 'attacking';
1508
-                this.attacking = true;
1509
-                this.updateTarget(); // Update target position for more accurate dive
1510
-            }
1511
-        } else if (this.state === 'attacking') {
1512
-            // FAST dive toward target
1513
-            let dx = this.targetX - this.x;
1514
-            this.vy = this.diveSpeed;
1515
-            this.vx = dx * 0.05; // Track horizontally too
1516
-            this.x += this.vx;
1517
-            this.y += this.vy;
1518
-            
1519
-            // Hit ground or passed target
1520
-            if (this.y > this.targetY + 20 || this.y > height - 50) {
1521
-                this.consecutiveAttacks++;
1522
-                
1523
-                // Do multiple attacks before retreating
1524
-                if (this.consecutiveAttacks < this.maxConsecutiveAttacks) {
1525
-                    // Quick pull up and attack again
1526
-                    this.state = 'approaching';
1527
-                    this.attacking = false;
1528
-                    this.y = min(this.y, height - 100); // Don't go too low
1529
-                    this.updateTarget(); // Get new target position
1530
-                } else {
1531
-                    // Finally retreat after multiple attacks
1532
-                    this.state = 'retreating';
1533
-                    this.attacking = false;
1534
-                }
1535
-            }
1536
-        } else if (this.state === 'retreating') {
1537
-            // Fly back up faster
1538
-            this.vy = -this.retreatSpeed;
1539
-            this.y += this.vy;
1540
-            this.x += sin(frameCount * 0.1) * 2; // Weave while retreating
1541
-            
1542
-            // Reset when off screen
1543
-            if (this.y < -50) {
1544
-                this.state = 'approaching';
1545
-                this.attackDelay = random(60, 120); // Shorter delay between attack runs
1546
-                this.x = random(width);
1547
-                this.consecutiveAttacks = 0; // Reset attack counter
1548
-                this.maxConsecutiveAttacks = random(2, 4); // Randomize next attack count
1549
-            }
1564
+  }
1565
+
1566
+  executeSwoopPattern () {
1567
+    if (this.state === 'approaching') {
1568
+      // Come from the side FAST
1569
+      if (this.x < 0) {
1570
+        this.x += 8 // Faster approach
1571
+        this.y = height * 0.3 + sin(this.x * 0.03) * 50
1572
+      } else {
1573
+        this.state = 'attacking'
1574
+        this.attacking = true
1575
+        this.updateTarget()
1576
+      }
1577
+    } else if (this.state === 'attacking') {
1578
+      // Swoop across screen following sine wave but faster
1579
+      this.x += 9 // Faster swoop
1580
+      this.y = height * 0.3 + sin(this.x * 0.03) * 120
1581
+
1582
+      // Track toward target when close
1583
+      if (abs(this.x - this.targetX) < 100) {
1584
+        // Aggressively dive toward target
1585
+        let dy = this.targetY - this.y
1586
+        this.y += dy * 0.2
1587
+      }
1588
+
1589
+      // Exit screen
1590
+      if (this.x > width + 50) {
1591
+        this.consecutiveAttacks++
1592
+
1593
+        if (this.consecutiveAttacks < this.maxConsecutiveAttacks) {
1594
+          // Come back from other side
1595
+          this.x = -50
1596
+          this.state = 'approaching'
1597
+          this.updateTarget()
1598
+        } else {
1599
+          this.state = 'retreating'
1600
+          this.attacking = false
15501601
         }
1602
+      }
1603
+    } else if (this.state === 'retreating') {
1604
+      // Reset
1605
+      this.state = 'approaching'
1606
+      this.attackDelay = random(90, 150)
1607
+      this.x = -50
1608
+      this.consecutiveAttacks = 0
1609
+      this.maxConsecutiveAttacks = random(2, 4)
15511610
     }
1552
-    
1553
-    executeSwoopPattern() {
1554
-        if (this.state === 'approaching') {
1555
-            // Come from the side FAST
1556
-            if (this.x < 0) {
1557
-                this.x += 8; // Faster approach
1558
-                this.y = height * 0.3 + sin(this.x * 0.03) * 50;
1559
-            } else {
1560
-                this.state = 'attacking';
1561
-                this.attacking = true;
1562
-                this.updateTarget();
1563
-            }
1564
-        } else if (this.state === 'attacking') {
1565
-            // Swoop across screen following sine wave but faster
1566
-            this.x += 9; // Faster swoop
1567
-            this.y = height * 0.3 + sin(this.x * 0.03) * 120;
1568
-            
1569
-            // Track toward target when close
1570
-            if (abs(this.x - this.targetX) < 100) {
1571
-                // Aggressively dive toward target
1572
-                let dy = this.targetY - this.y;
1573
-                this.y += dy * 0.2;
1574
-            }
1575
-            
1576
-            // Exit screen
1577
-            if (this.x > width + 50) {
1578
-                this.consecutiveAttacks++;
1579
-                
1580
-                if (this.consecutiveAttacks < this.maxConsecutiveAttacks) {
1581
-                    // Come back from other side
1582
-                    this.x = -50;
1583
-                    this.state = 'approaching';
1584
-                    this.updateTarget();
1585
-                } else {
1586
-                    this.state = 'retreating';
1587
-                    this.attacking = false;
1588
-                }
1589
-            }
1590
-        } else if (this.state === 'retreating') {
1591
-            // Reset
1592
-            this.state = 'approaching';
1593
-            this.attackDelay = random(90, 150);
1594
-            this.x = -50;
1595
-            this.consecutiveAttacks = 0;
1596
-            this.maxConsecutiveAttacks = random(2, 4);
1611
+  }
1612
+
1613
+  executeGlidePattern () {
1614
+    if (this.state === 'approaching') {
1615
+      // Glide in from top corner faster
1616
+      this.x += 5
1617
+      this.y += 2.5
1618
+
1619
+      if (this.y > height * 0.15) {
1620
+        this.state = 'attacking'
1621
+        this.attacking = true
1622
+        this.updateTarget()
1623
+      }
1624
+    } else if (this.state === 'attacking') {
1625
+      // Glide toward target aggressively
1626
+      let dx = this.targetX - this.x
1627
+      let dy = this.targetY - this.y
1628
+      let dist = sqrt(dx * dx + dy * dy)
1629
+
1630
+      if (dist > 10) {
1631
+        this.x += (dx / dist) * 7 // Much faster glide
1632
+        this.y += (dy / dist) * 7
1633
+      }
1634
+
1635
+      // Pass through and maybe attack again
1636
+      if (this.y > height - 100 || this.x < -50 || this.x > width + 50) {
1637
+        this.consecutiveAttacks++
1638
+
1639
+        if (this.consecutiveAttacks < this.maxConsecutiveAttacks) {
1640
+          // Reset for another pass
1641
+          this.state = 'approaching'
1642
+          this.x = random() < 0.5 ? -50 : width + 50
1643
+          this.y = random(50, 150)
1644
+          this.updateTarget()
1645
+        } else {
1646
+          this.state = 'retreating'
1647
+          this.attacking = false
15971648
         }
1649
+      }
1650
+    } else if (this.state === 'retreating') {
1651
+      // Continue off screen
1652
+      this.x += this.vx
1653
+      this.y += this.vy
1654
+
1655
+      // Reset
1656
+      if (this.y > height + 50 || this.x < -100 || this.x > width + 100) {
1657
+        this.state = 'approaching'
1658
+        this.attackDelay = random(120, 180)
1659
+        this.x = random() < 0.5 ? -50 : width + 50
1660
+        this.y = random(50, 150)
1661
+        this.vx = this.x < width / 2 ? 5 : -5
1662
+        this.vy = 2.5
1663
+        this.consecutiveAttacks = 0
1664
+        this.maxConsecutiveAttacks = random(2, 4)
1665
+      }
15981666
     }
1599
-    
1600
-    executeGlidePattern() {
1601
-        if (this.state === 'approaching') {
1602
-            // Glide in from top corner faster
1603
-            this.x += 5;
1604
-            this.y += 2.5;
1605
-            
1606
-            if (this.y > height * 0.15) {
1607
-                this.state = 'attacking';
1608
-                this.attacking = true;
1609
-                this.updateTarget();
1610
-            }
1611
-        } else if (this.state === 'attacking') {
1612
-            // Glide toward target aggressively
1613
-            let dx = this.targetX - this.x;
1614
-            let dy = this.targetY - this.y;
1615
-            let dist = sqrt(dx * dx + dy * dy);
1616
-            
1617
-            if (dist > 10) {
1618
-                this.x += (dx / dist) * 7; // Much faster glide
1619
-                this.y += (dy / dist) * 7;
1620
-            }
1621
-            
1622
-            // Pass through and maybe attack again
1623
-            if (this.y > height - 100 || this.x < -50 || this.x > width + 50) {
1624
-                this.consecutiveAttacks++;
1625
-                
1626
-                if (this.consecutiveAttacks < this.maxConsecutiveAttacks) {
1627
-                    // Reset for another pass
1628
-                    this.state = 'approaching';
1629
-                    this.x = random() < 0.5 ? -50 : width + 50;
1630
-                    this.y = random(50, 150);
1631
-                    this.updateTarget();
1632
-                } else {
1633
-                    this.state = 'retreating';
1634
-                    this.attacking = false;
1635
-                }
1636
-            }
1637
-        } else if (this.state === 'retreating') {
1638
-            // Continue off screen
1639
-            this.x += this.vx;
1640
-            this.y += this.vy;
1641
-            
1642
-            // Reset
1643
-            if (this.y > height + 50 || this.x < -100 || this.x > width + 100) {
1644
-                this.state = 'approaching';
1645
-                this.attackDelay = random(120, 180);
1646
-                this.x = random() < 0.5 ? -50 : width + 50;
1647
-                this.y = random(50, 150);
1648
-                this.vx = this.x < width/2 ? 5 : -5;
1649
-                this.vy = 2.5;
1650
-                this.consecutiveAttacks = 0;
1651
-                this.maxConsecutiveAttacks = random(2, 4);
1652
-            }
1653
-        }
1667
+  }
1668
+
1669
+  executeCirclePattern () {
1670
+    if (this.state === 'approaching') {
1671
+      // Move to circle start position
1672
+      let startX = this.circleCenter.x + cos(0) * this.circleRadius
1673
+      let startY = this.circleCenter.y + sin(0) * this.circleRadius
1674
+
1675
+      let dx = startX - this.x
1676
+      let dy = startY - this.y
1677
+
1678
+      this.x += dx * 0.1
1679
+      this.y += dy * 0.1
1680
+
1681
+      if (abs(dx) < 20 && abs(dy) < 20) {
1682
+        this.state = 'attacking'
1683
+        this.attacking = true
1684
+        this.circleAngle = 0
1685
+      }
1686
+    } else if (this.state === 'attacking') {
1687
+      // Circle around center FASTER
1688
+      this.circleAngle += 0.08 // Faster circling
1689
+      this.x = this.circleCenter.x + cos(this.circleAngle) * this.circleRadius
1690
+      this.y = this.circleCenter.y + sin(this.circleAngle) * this.circleRadius
1691
+
1692
+      // More frequent dives toward center
1693
+      if (frameCount % 60 === 0) {
1694
+        // Every second instead of every 2 seconds
1695
+        this.circleRadius = max(30, this.circleRadius - 50)
1696
+      } else {
1697
+        this.circleRadius = min(150, this.circleRadius + 2)
1698
+      }
1699
+
1700
+      // Complete circle faster
1701
+      if (this.circleAngle > TWO_PI * 1.5) {
1702
+        // 1.5 circles instead of 2
1703
+        this.state = 'retreating'
1704
+        this.attacking = false
1705
+      }
1706
+    } else if (this.state === 'retreating') {
1707
+      // Fly away
1708
+      this.y -= 7
1709
+
1710
+      if (this.y < -50) {
1711
+        this.state = 'approaching'
1712
+        this.attackDelay = random(150, 240)
1713
+        this.x = random(width)
1714
+      }
16541715
     }
1716
+  }
1717
+
1718
+checkCollisions() {
1719
+    // FIX: Increased collision radius for more generous hit detection
1720
+    let collisionDistance = this.size + spider.radius + 5; // Added 5 pixel buffer
16551721
     
1656
-    executeCirclePattern() {
1657
-        if (this.state === 'approaching') {
1658
-            // Move to circle start position
1659
-            let startX = this.circleCenter.x + cos(0) * this.circleRadius;
1660
-            let startY = this.circleCenter.y + sin(0) * this.circleRadius;
1722
+    // Check collision with spider
1723
+    if (this.attacking && dist(this.x, this.y, spider.pos.x, spider.pos.y) < collisionDistance) {
1724
+        // Hit spider!
1725
+        if (gamePhase === 'DAWN') {
1726
+            // Calculate damage
1727
+            let damage = 20; // Base damage
16611728
             
1662
-            let dx = startX - this.x;
1663
-            let dy = startY - this.y;
1729
+            // If spider has no stamina, GAME OVER!
1730
+            if (jumpStamina <= 0) {
1731
+                triggerGameOver('Exhausted spider caught by bird!');
1732
+                return;
1733
+            }
16641734
             
1665
-            this.x += dx * 0.1;
1666
-            this.y += dy * 0.1;
1735
+            // Otherwise, reduce stamina
1736
+            jumpStamina = max(0, jumpStamina - damage);
1737
+            stats.birdHitsTaken++;
16671738
             
1668
-            if (abs(dx) < 20 && abs(dy) < 20) {
1669
-                this.state = 'attacking';
1670
-                this.attacking = true;
1671
-                this.circleAngle = 0;
1672
-            }
1673
-        } else if (this.state === 'attacking') {
1674
-            // Circle around center FASTER
1675
-            this.circleAngle += 0.08; // Faster circling
1676
-            this.x = this.circleCenter.x + cos(this.circleAngle) * this.circleRadius;
1677
-            this.y = this.circleCenter.y + sin(this.circleAngle) * this.circleRadius;
1739
+            // Knockback effect
1740
+            spider.vel.x = (spider.pos.x - this.x) * 0.3;
1741
+            spider.vel.y = -3;
1742
+            spider.isAirborne = true;
16781743
             
1679
-            // More frequent dives toward center
1680
-            if (frameCount % 60 === 0) { // Every second instead of every 2 seconds
1681
-                this.circleRadius = max(30, this.circleRadius - 50);
1682
-            } else {
1683
-                this.circleRadius = min(150, this.circleRadius + 2);
1744
+            // Red damage particles
1745
+            for (let i = 0; i < 12; i++) {
1746
+                let p = new Particle(spider.pos.x, spider.pos.y);
1747
+                p.color = color(255, 50, 50);
1748
+                p.vel = createVector(random(-4, 4), random(-4, 1));
1749
+                p.size = random(4, 8);
1750
+                particles.push(p);
16841751
             }
16851752
             
1686
-            // Complete circle faster
1687
-            if (this.circleAngle > TWO_PI * 1.5) { // 1.5 circles instead of 2
1688
-                this.state = 'retreating';
1689
-                this.attacking = false;
1753
+            // Screen shake effect
1754
+            if (typeof screenShake !== 'undefined') {
1755
+                screenShake = 10;
16901756
             }
1691
-        } else if (this.state === 'retreating') {
1692
-            // Fly away
1693
-            this.y -= 7;
16941757
             
1695
-            if (this.y < -50) {
1696
-                this.state = 'approaching';
1697
-                this.attackDelay = random(150, 240);
1698
-                this.x = random(width);
1699
-            }
1700
-        }
1701
-    }
1702
-
1703
-    checkCollisions() {
1704
-        // Check collision with spider
1705
-        if (this.attacking && dist(this.x, this.y, spider.pos.x, spider.pos.y) < this.size + spider.radius) {
1706
-            // Hit spider!
1707
-            if (gamePhase === 'DAWN') {
1708
-                // Calculate damage based on remaining stamina
1709
-                let damage = 20; // Base damage
1710
-                
1711
-                // If spider has no stamina, GAME OVER!
1712
-                if (jumpStamina <= 0) {
1713
-                    triggerGameOver('Exhausted spider caught by bird!');
1714
-                    return;
1715
-                }
1716
-                
1717
-                // Otherwise, reduce stamina
1718
-                jumpStamina = max(0, jumpStamina - damage);
1719
-                stats.birdHitsTaken++;
1720
-                
1721
-                // Knockback effect
1722
-                spider.vel.x = (spider.pos.x - this.x) * 0.3;
1723
-                spider.vel.y = -3;
1724
-                spider.isAirborne = true;
1725
-                
1726
-                // Red damage particles
1727
-                for (let i = 0; i < 12; i++) {
1728
-                    let p = new Particle(spider.pos.x, spider.pos.y);
1729
-                    p.color = color(255, 50, 50);
1730
-                    p.vel = createVector(random(-4, 4), random(-4, 1));
1731
-                    p.size = random(4, 8);
1732
-                    particles.push(p);
1733
-                }
1734
-                
1735
-                // Screen shake effect
1736
-                screenShake = 10;
1737
-                
1738
-                // Warning notification if stamina is low
1758
+            // Warning notifications - but limited to prevent spam
1759
+            if (notifications.length < 3) { // Limit notifications
17391760
                 if (jumpStamina <= 20) {
17401761
                     notifications.push(new Notification("CRITICAL STAMINA!", color(255, 50, 50)));
17411762
                 } else if (jumpStamina <= 40) {
17421763
                     notifications.push(new Notification("Low stamina - find cover!", color(255, 150, 50)));
17431764
                 }
17441765
             }
1745
-            
1746
-            // Bird bounces off
1747
-            this.state = 'retreating';
1748
-            this.attacking = false;
17491766
         }
1767
+        
1768
+        // Bird bounces off
1769
+        this.state = 'retreating';
1770
+        this.attacking = false;
1771
+    }
17501772
 
17511773
     // Check collision with web strands
17521774
     if (this.attacking) {
js/game.jsmodified
@@ -18,6 +18,7 @@ let gameOver = false
1818
 let gameOverTimer = 0
1919
 let deathReason = ''
2020
 let finalScore = 0
21
+let screenShake = 0
2122
 
2223
 // Resources
2324
 let webSilk = 100
@@ -320,27 +321,50 @@ class Notification {
320321
   constructor (text, color) {
321322
     this.text = text
322323
     this.color = color
323
-    this.y = height * 0.3
324
-    this.alpha = 255
325324
     this.lifetime = 180 // 3 seconds
325
+    this.alpha = 255
326
+
327
+    // IMPROVED: Stacking system to prevent overlap
328
+    // Find how many notifications are currently active
329
+    let activeNotifications = notifications.filter(n => n.lifetime > 60).length
330
+
331
+    // Stack notifications vertically
332
+    this.y = height * 0.3 + activeNotifications * 35 // 35 pixels between notifications
333
+
334
+    // Prevent too many notifications
335
+    if (notifications.length > 5) {
336
+      notifications.shift() // Remove oldest
337
+    }
326338
   }
327339
 
328340
   update () {
329341
     this.lifetime--
342
+
343
+    // Fade out in the last second
330344
     if (this.lifetime < 60) {
331345
       this.alpha = map(this.lifetime, 0, 60, 0, 255)
332346
     }
333
-    this.y -= 0.5 // Slowly rise
347
+
348
+    // Slowly rise
349
+    this.y -= 0.3 // Slower rise to maintain readability
334350
   }
335351
 
336352
   display () {
337353
     push()
338354
     textAlign(CENTER)
339
-    textSize(24)
340
-    strokeWeight(4)
355
+
356
+    // Add background for better readability
357
+    fill(0, 0, 0, this.alpha * 0.5)
358
+    noStroke()
359
+    rectMode(CENTER)
360
+    rect(width / 2, this.y, textWidth(this.text) + 20, 30, 5)
361
+
362
+    // Text with outline for visibility
363
+    textSize(20) // Slightly smaller for less overlap
364
+    strokeWeight(3)
341365
     stroke(0, 0, 0, this.alpha)
342366
     fill(red(this.color), green(this.color), blue(this.color), this.alpha)
343
-    text(this.text, width / 2, this.y)
367
+    text(this.text, width / 2, this.y + 5)
344368
     pop()
345369
   }
346370
 
@@ -662,45 +686,36 @@ function draw () {
662686
     gamePhase = 'DAWN'
663687
     phaseTimer = 0
664688
 
665
-    // ENHANCED: Calculate dawn stamina with CLEAR BONUS DISPLAY
689
+    // Calculate dawn stamina with CLEAR BONUS DISPLAY
666690
     let baseStamina = 30
667691
     let flyBonus = fliesMunchedLastNight * 10
668
-    staminaBonus = flyBonus // Store for display
692
+    staminaBonus = flyBonus
669693
 
670694
     maxJumpStamina = baseStamina + flyBonus
671
-    maxJumpStamina = min(maxJumpStamina, 200) // Cap at 200 instead of 150
695
+    maxJumpStamina = min(maxJumpStamina, 200)
672696
     jumpStamina = maxJumpStamina
673697
 
674
-    // Clear notification showing stamina calculation
675
-    notifications.push(
676
-      new Notification(
677
-        `Dawn Stamina: ${baseStamina} base + ${flyBonus} (${fliesMunchedLastNight} flies × 10) = ${maxJumpStamina}`,
678
-        color(255, 200, 100)
679
-      )
680
-    )
698
+    // IMPROVED: Combine notifications to reduce overlap
699
+    let staminaMessage = `Dawn: ${maxJumpStamina} stamina (${baseStamina} + ${flyBonus} from ${fliesMunchedLastNight} flies)`
700
+    let warningMessage = ''
681701
 
682
-    // Warning notifications based on stamina level
683702
     if (maxJumpStamina <= 50) {
703
+      warningMessage = ' ⚠️ DANGER!'
684704
       notifications.push(
685
-        new Notification(
686
-          '⚠️ DANGER: Very low stamina! Eat more flies next night!',
687
-          color(255, 50, 50)
688
-        )
705
+        new Notification(staminaMessage + warningMessage, color(255, 50, 50))
689706
       )
690707
     } else if (maxJumpStamina <= 80) {
708
+      warningMessage = ' - Low stamina!'
691709
       notifications.push(
692
-        new Notification(
693
-          'Warning: Low stamina for dawn survival',
694
-          color(255, 150, 50)
695
-        )
710
+        new Notification(staminaMessage + warningMessage, color(255, 150, 50))
696711
       )
697712
     } else if (maxJumpStamina >= 150) {
713
+      warningMessage = ' - Well fed!'
698714
       notifications.push(
699
-        new Notification(
700
-          'Excellent stamina! Well fed spider!',
701
-          color(100, 255, 100)
702
-        )
715
+        new Notification(staminaMessage + warningMessage, color(100, 255, 100))
703716
       )
717
+    } else {
718
+      notifications.push(new Notification(staminaMessage, color(255, 200, 100)))
704719
     }
705720
 
706721
     // Spawn birds