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 {
513
     }
513
     }
514
   }
514
   }
515
 
515
 
516
-    checkWebCollisions() {
516
+  checkWebCollisions () {
517
-    let currentlyTouching = new Set();
517
+    let currentlyTouching = new Set()
518
 
518
 
519
     for (let strand of webStrands) {
519
     for (let strand of webStrands) {
520
-      let touching = false;
520
+      let touching = false
521
 
521
 
522
       // Check collision with strand path
522
       // Check collision with strand path
523
       if (strand.path && strand.path.length > 1) {
523
       if (strand.path && strand.path.length > 1) {
524
         // OPTIMIZATION: Skip every other point for collision detection
524
         // OPTIMIZATION: Skip every other point for collision detection
525
         for (let i = 0; i < strand.path.length - 1; i += 2) {
525
         for (let i = 0; i < strand.path.length - 1; i += 2) {
526
-          let p1 = strand.path[i];
526
+          let p1 = strand.path[i]
527
-          let p2 = strand.path[Math.min(i + 1, strand.path.length - 1)];
527
+          let p2 = strand.path[Math.min(i + 1, strand.path.length - 1)]
528
-          let d = this.pointToLineDistance(this.pos, p1, p2);
528
+          let d = this.pointToLineDistance(this.pos, p1, p2)
529
           if (d < this.radius + 3) {
529
           if (d < this.radius + 3) {
530
-            touching = true;
530
+            touching = true
531
-            break;
531
+            break
532
           }
532
           }
533
         }
533
         }
534
       } else if (strand.start && strand.end) {
534
       } else if (strand.start && strand.end) {
535
         // Fallback for strands without path
535
         // 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)
537
         if (d < this.radius + 3) {
537
         if (d < this.radius + 3) {
538
-          touching = true;
538
+          touching = true
539
         }
539
         }
540
       }
540
       }
541
 
541
 
542
       if (touching) {
542
       if (touching) {
543
-        currentlyTouching.add(strand);
543
+        currentlyTouching.add(strand)
544
 
544
 
545
         // If this is a new strand we're touching
545
         // If this is a new strand we're touching
546
         if (!this.touchedStrands.has(strand)) {
546
         if (!this.touchedStrands.has(strand)) {
547
-          this.touchedStrands.add(strand);
547
+          this.touchedStrands.add(strand)
548
 
548
 
549
           // Vibrate the web when first touching
549
           // Vibrate the web when first touching
550
-          strand.vibrate(3);
550
+          strand.vibrate(3)
551
 
551
 
552
           // First strand slows us down
552
           // First strand slows us down
553
           if (this.touchedStrands.size === 1) {
553
           if (this.touchedStrands.size === 1) {
554
-            this.currentSpeed = this.baseSpeed * 0.4; // Slow to 40% speed
554
+            this.currentSpeed = this.baseSpeed * 0.4 // Slow to 40% speed
555
-            this.slowedBy.add(strand);
555
+            this.slowedBy.add(strand)
556
 
556
 
557
             // Visual feedback - yellow particles for slowing
557
             // Visual feedback - yellow particles for slowing
558
             // LIMIT PARTICLES TO PREVENT FREEZE
558
             // LIMIT PARTICLES TO PREVENT FREEZE
559
-            let particleCount = Math.min(3, 100 - particles.length);
559
+            let particleCount = Math.min(3, 100 - particles.length)
560
             for (let j = 0; j < particleCount; j++) {
560
             for (let j = 0; j < particleCount; j++) {
561
-              let p = new Particle(this.pos.x, this.pos.y);
561
+              let p = new Particle(this.pos.x, this.pos.y)
562
-              p.color = color(255, 255, 0, 150);
562
+              p.color = color(255, 255, 0, 150)
563
-              p.vel = createVector(random(-1, 1), random(-1, 1));
563
+              p.vel = createVector(random(-1, 1), random(-1, 1))
564
-              p.size = 3;
564
+              p.size = 3
565
-              particles.push(p);
565
+              particles.push(p)
566
             }
566
             }
567
           }
567
           }
568
           // Second strand catches us
568
           // Second strand catches us
569
           else if (this.touchedStrands.size >= 2 && !this.caught) {
569
           else if (this.touchedStrands.size >= 2 && !this.caught) {
570
-            this.caught = true;
570
+            this.caught = true
571
-            this.currentSpeed = 0;
571
+            this.currentSpeed = 0
572
 
572
 
573
             // Stronger vibration when caught
573
             // Stronger vibration when caught
574
-            strand.vibrate(8);
574
+            strand.vibrate(8)
575
 
575
 
576
             // FIX: OPTIMIZE NEARBY STRAND VIBRATION
576
             // FIX: OPTIMIZE NEARBY STRAND VIBRATION
577
             // This is likely the main cause of the freeze - checking distances between all strands
577
             // This is likely the main cause of the freeze - checking distances between all strands
578
             // Use a more efficient method
578
             // Use a more efficient method
579
-            propagateVibration(strand, 2);
579
+            propagateVibration(strand, 2)
580
 
580
 
581
             // Create caught particles - LIMIT TO PREVENT FREEZE
581
             // 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)
583
             for (let j = 0; j < particleCount; j++) {
583
             for (let j = 0; j < particleCount; j++) {
584
-              let p = new Particle(this.pos.x, this.pos.y);
584
+              let p = new Particle(this.pos.x, this.pos.y)
585
-              p.color = color(255, 200, 0, 200);
585
+              p.color = color(255, 200, 0, 200)
586
-              p.vel = createVector(random(-2, 2), random(-2, 2));
586
+              p.vel = createVector(random(-2, 2), random(-2, 2))
587
-              particles.push(p);
587
+              particles.push(p)
588
             }
588
             }
589
           }
589
           }
590
         }
590
         }
@@ -593,32 +593,34 @@ class Fly {
593
 
593
 
594
     // If we're no longer touching strands we were slowed by, speed back up
594
     // If we're no longer touching strands we were slowed by, speed back up
595
     if (this.slowedBy.size > 0 && currentlyTouching.size === 0) {
595
     if (this.slowedBy.size > 0 && currentlyTouching.size === 0) {
596
-      this.currentSpeed = this.baseSpeed;
596
+      this.currentSpeed = this.baseSpeed
597
-      this.slowedBy.clear();
597
+      this.slowedBy.clear()
598
     }
598
     }
599
   }
599
   }
600
 
600
 
601
-  pointToLineDistance(point, lineStart, lineEnd) {
601
+  pointToLineDistance (point, lineStart, lineEnd) {
602
     // Add null checks
602
     // Add null checks
603
-    if (!point || !lineStart || !lineEnd) return Infinity;
603
+    if (!point || !lineStart || !lineEnd) return Infinity
604
-    
604
+
605
-    let dx = lineEnd.x - lineStart.x;
605
+    let dx = lineEnd.x - lineStart.x
606
-    let dy = lineEnd.y - lineStart.y;
606
+    let dy = lineEnd.y - lineStart.y
607
-    let lineLength = sqrt(dx * dx + dy * dy);
607
+    let lineLength = sqrt(dx * dx + dy * dy)
608
-    
608
+
609
     // If line has no length, return distance to point
609
     // If line has no length, return distance to point
610
     if (lineLength < 0.01) {
610
     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)
612
     }
612
     }
613
-    
613
+
614
     // Use optimized calculation without creating new vectors
614
     // Use optimized calculation without creating new vectors
615
-    let t = ((point.x - lineStart.x) * dx + (point.y - lineStart.y) * dy) / (lineLength * lineLength);
615
+    let t =
616
-    t = constrain(t, 0, 1);
616
+      ((point.x - lineStart.x) * dx + (point.y - lineStart.y) * dy) /
617
-    
617
+      (lineLength * lineLength)
618
-    let closestX = lineStart.x + t * dx;
618
+    t = constrain(t, 0, 1)
619
-    let closestY = lineStart.y + t * dy;
619
+
620
-    
620
+    let closestX = lineStart.x + t * dx
621
-    return dist(point.x, point.y, closestX, closestY);
621
+    let closestY = lineStart.y + t * dy
622
+
623
+    return dist(point.x, point.y, closestX, closestY)
622
   }
624
   }
623
 
625
 
624
   display () {
626
   display () {
@@ -1374,379 +1376,399 @@ class FoodBox {
1374
 }
1376
 }
1375
 
1377
 
1376
 class Bird {
1378
 class Bird {
1377
-    constructor(pattern, isThief = false) {
1379
+  constructor (pattern, isThief = false) {
1378
-        this.pattern = pattern; // 'dive', 'swoop', 'glide', 'circle'
1380
+    this.pattern = pattern // 'dive', 'swoop', 'glide', 'circle'
1379
-        this.isThief = isThief;
1381
+    this.isThief = isThief
1380
-        this.active = false;
1382
+    this.active = false
1381
-        this.attacking = false;
1383
+    this.attacking = false
1382
-        this.attackDelay = isThief ? 120 : random(30, 90); // MUCH shorter initial delay
1384
+    this.attackDelay = isThief ? 120 : random(30, 90) // MUCH shorter initial delay
1383
-        
1385
+
1384
-        // Position and movement
1386
+    // Position and movement
1385
-        this.x = random(width);
1387
+    this.x = random(width)
1386
-        this.y = -50; // Start above screen
1388
+    this.y = -50 // Start above screen
1387
-        this.vx = 0;
1389
+    this.vx = 0
1388
-        this.vy = 0;
1390
+    this.vy = 0
1389
-        this.targetX = 0;
1391
+    this.targetX = 0
1390
-        this.targetY = 0;
1392
+    this.targetY = 0
1391
-        this.speed = 5; // Increased from 3
1393
+    this.speed = 5 // Increased from 3
1392
-        this.angle = 0;
1394
+    this.angle = 0
1393
-        this.wingPhase = random(TWO_PI);
1395
+    this.wingPhase = random(TWO_PI)
1394
-        
1396
+
1395
-        // Visual properties
1397
+    // Visual properties
1396
-        this.size = isThief ? 25 : 20;
1398
+    this.size = isThief ? 25 : 20
1397
-        this.color = isThief ? color(100, 50, 150) : color(50, 50, 50);
1399
+    this.color = isThief ? color(100, 50, 150) : color(50, 50, 50)
1398
-        
1400
+
1399
-        // Pattern-specific properties
1401
+    // Pattern-specific properties
1400
-        if (pattern === 'circle') {
1402
+    if (pattern === 'circle') {
1401
-            this.circleRadius = 150;
1403
+      this.circleRadius = 150
1402
-            this.circleAngle = 0;
1404
+      this.circleAngle = 0
1403
-            this.circleCenter = createVector(width/2, height/2);
1405
+      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
1412
     }
1406
     }
1413
-    
1407
+
1414
-    update() {
1408
+    // Attack properties - MUCH MORE AGGRESSIVE
1415
-        // Update wing animation
1409
+    this.diveSpeed = 12 // Increased from 8
1416
-        this.wingPhase += 0.3; // Faster wing flapping
1410
+    this.retreatSpeed = 6 // Increased from 4
1417
-        
1411
+    this.state = 'waiting' // 'waiting', 'approaching', 'attacking', 'retreating'
1418
-        // Countdown to attack - MUCH FASTER
1412
+    this.consecutiveAttacks = 0 // Track multiple attacks
1419
-        if (this.attackDelay > 0) {
1413
+    this.maxConsecutiveAttacks = random(2, 4) // Each bird does 2-4 attacks before retreating
1420
-            this.attackDelay--;
1414
+  }
1421
-            // Hover while waiting - more aggressive hovering
1415
+
1422
-            this.y = -30 + sin(frameCount * 0.08) * 15;
1416
+  update () {
1423
-            this.x += sin(frameCount * 0.05) * 3;
1417
+    // Update wing animation
1424
-            
1418
+    this.wingPhase += 0.3 // Faster wing flapping
1425
-            // Show warning when about to attack
1419
+
1426
-            if (this.attackDelay < 30) {
1420
+    // Countdown to attack - MUCH FASTER
1427
-                this.y = lerp(this.y, 50, 0.1); // Start moving into view
1421
+    if (this.attackDelay > 0) {
1428
-            }
1422
+      this.attackDelay--
1429
-            return;
1423
+      // Hover while waiting - more aggressive hovering
1430
-        }
1424
+      this.y = -30 + sin(frameCount * 0.08) * 15
1431
-        
1425
+      this.x += sin(frameCount * 0.05) * 3
1432
-        // Activate after delay
1426
+
1433
-        if (!this.active) {
1427
+      // Show warning when about to attack
1434
-            this.active = true;
1428
+      if (this.attackDelay < 30) {
1435
-            this.state = 'approaching';
1429
+        this.y = lerp(this.y, 50, 0.1) // Start moving into view
1436
-            this.updateTarget(); // Set initial target
1430
+      }
1437
-        }
1431
+      return
1438
-        
1432
+    }
1439
-        // Execute movement pattern
1433
+
1440
-        switch(this.pattern) {
1434
+    // Activate after delay
1441
-            case 'dive':
1435
+    if (!this.active) {
1442
-                this.executeDivePattern();
1436
+      this.active = true
1443
-                break;
1437
+      this.state = 'approaching'
1444
-            case 'swoop':
1438
+      this.updateTarget() // Set initial target
1445
-                this.executeSwoopPattern();
1439
+    }
1446
-                break;
1440
+
1447
-            case 'glide':
1441
+    // Execute movement pattern
1448
-                this.executeGlidePattern();
1442
+    switch (this.pattern) {
1449
-                break;
1443
+      case 'dive':
1450
-            case 'circle':
1444
+        this.executeDivePattern()
1451
-                this.executeCirclePattern();
1445
+        break
1452
-                break;
1446
+      case 'swoop':
1453
-        }
1447
+        this.executeSwoopPattern()
1454
-        
1448
+        break
1455
-        // Check collisions
1449
+      case 'glide':
1456
-        this.checkCollisions();
1450
+        this.executeGlidePattern()
1457
-        
1451
+        break
1458
-        // Keep on screen during approach
1452
+      case 'circle':
1459
-        if (this.state === 'approaching') {
1453
+        this.executeCirclePattern()
1460
-            this.x = constrain(this.x, 20, width - 20);
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
+          }
1461
         }
1493
         }
1494
+      }
1462
     }
1495
     }
1463
-    
1496
+  }
1464
-    updateTarget() {
1497
+
1465
-        if (this.isThief) {
1498
+  executeDivePattern () {
1466
-            // Target caught flies
1499
+    if (this.state === 'approaching') {
1467
-            let caughtFlies = flies.filter(f => f.stuck || f.caught);
1500
+      // Move into position above target MUCH FASTER
1468
-            if (caughtFlies.length > 0) {
1501
+      let dx = this.targetX - this.x
1469
-                let target = random(caughtFlies);
1502
+      let dy = 50 - this.y // Lower starting position for faster attack
1470
-                this.targetX = target.pos.x;
1503
+
1471
-                this.targetY = target.pos.y;
1504
+      this.x += dx * 0.15 // Much faster positioning
1472
-            } else {
1505
+      this.y += dy * 0.15
1473
-                this.active = false; // No targets, deactivate
1506
+
1474
-                return;
1507
+      // When in position, start diving immediately
1475
-            }
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
1476
         } else {
1543
         } else {
1477
-            // Heavily favor targeting spider (90% chance)
1544
+          // Finally retreat after multiple attacks
1478
-            if (random() < 0.9) {
1545
+          this.state = 'retreating'
1479
-                // Target spider with prediction
1546
+          this.attacking = false
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
-            }
1493
         }
1547
         }
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
+      }
1494
     }
1563
     }
1495
-    
1564
+  }
1496
-    executeDivePattern() {
1565
+
1497
-        if (this.state === 'approaching') {
1566
+  executeSwoopPattern () {
1498
-            // Move into position above target MUCH FASTER
1567
+    if (this.state === 'approaching') {
1499
-            let dx = this.targetX - this.x;
1568
+      // Come from the side FAST
1500
-            let dy = 50 - this.y; // Lower starting position for faster attack
1569
+      if (this.x < 0) {
1501
-            
1570
+        this.x += 8 // Faster approach
1502
-            this.x += dx * 0.15; // Much faster positioning
1571
+        this.y = height * 0.3 + sin(this.x * 0.03) * 50
1503
-            this.y += dy * 0.15;
1572
+      } else {
1504
-            
1573
+        this.state = 'attacking'
1505
-            // When in position, start diving immediately
1574
+        this.attacking = true
1506
-            if (abs(dx) < 50 && abs(dy) < 30) {
1575
+        this.updateTarget()
1507
-                this.state = 'attacking';
1576
+      }
1508
-                this.attacking = true;
1577
+    } else if (this.state === 'attacking') {
1509
-                this.updateTarget(); // Update target position for more accurate dive
1578
+      // Swoop across screen following sine wave but faster
1510
-            }
1579
+      this.x += 9 // Faster swoop
1511
-        } else if (this.state === 'attacking') {
1580
+      this.y = height * 0.3 + sin(this.x * 0.03) * 120
1512
-            // FAST dive toward target
1581
+
1513
-            let dx = this.targetX - this.x;
1582
+      // Track toward target when close
1514
-            this.vy = this.diveSpeed;
1583
+      if (abs(this.x - this.targetX) < 100) {
1515
-            this.vx = dx * 0.05; // Track horizontally too
1584
+        // Aggressively dive toward target
1516
-            this.x += this.vx;
1585
+        let dy = this.targetY - this.y
1517
-            this.y += this.vy;
1586
+        this.y += dy * 0.2
1518
-            
1587
+      }
1519
-            // Hit ground or passed target
1588
+
1520
-            if (this.y > this.targetY + 20 || this.y > height - 50) {
1589
+      // Exit screen
1521
-                this.consecutiveAttacks++;
1590
+      if (this.x > width + 50) {
1522
-                
1591
+        this.consecutiveAttacks++
1523
-                // Do multiple attacks before retreating
1592
+
1524
-                if (this.consecutiveAttacks < this.maxConsecutiveAttacks) {
1593
+        if (this.consecutiveAttacks < this.maxConsecutiveAttacks) {
1525
-                    // Quick pull up and attack again
1594
+          // Come back from other side
1526
-                    this.state = 'approaching';
1595
+          this.x = -50
1527
-                    this.attacking = false;
1596
+          this.state = 'approaching'
1528
-                    this.y = min(this.y, height - 100); // Don't go too low
1597
+          this.updateTarget()
1529
-                    this.updateTarget(); // Get new target position
1598
+        } else {
1530
-                } else {
1599
+          this.state = 'retreating'
1531
-                    // Finally retreat after multiple attacks
1600
+          this.attacking = false
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
-            }
1550
         }
1601
         }
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)
1551
     }
1610
     }
1552
-    
1611
+  }
1553
-    executeSwoopPattern() {
1612
+
1554
-        if (this.state === 'approaching') {
1613
+  executeGlidePattern () {
1555
-            // Come from the side FAST
1614
+    if (this.state === 'approaching') {
1556
-            if (this.x < 0) {
1615
+      // Glide in from top corner faster
1557
-                this.x += 8; // Faster approach
1616
+      this.x += 5
1558
-                this.y = height * 0.3 + sin(this.x * 0.03) * 50;
1617
+      this.y += 2.5
1559
-            } else {
1618
+
1560
-                this.state = 'attacking';
1619
+      if (this.y > height * 0.15) {
1561
-                this.attacking = true;
1620
+        this.state = 'attacking'
1562
-                this.updateTarget();
1621
+        this.attacking = true
1563
-            }
1622
+        this.updateTarget()
1564
-        } else if (this.state === 'attacking') {
1623
+      }
1565
-            // Swoop across screen following sine wave but faster
1624
+    } else if (this.state === 'attacking') {
1566
-            this.x += 9; // Faster swoop
1625
+      // Glide toward target aggressively
1567
-            this.y = height * 0.3 + sin(this.x * 0.03) * 120;
1626
+      let dx = this.targetX - this.x
1568
-            
1627
+      let dy = this.targetY - this.y
1569
-            // Track toward target when close
1628
+      let dist = sqrt(dx * dx + dy * dy)
1570
-            if (abs(this.x - this.targetX) < 100) {
1629
+
1571
-                // Aggressively dive toward target
1630
+      if (dist > 10) {
1572
-                let dy = this.targetY - this.y;
1631
+        this.x += (dx / dist) * 7 // Much faster glide
1573
-                this.y += dy * 0.2;
1632
+        this.y += (dy / dist) * 7
1574
-            }
1633
+      }
1575
-            
1634
+
1576
-            // Exit screen
1635
+      // Pass through and maybe attack again
1577
-            if (this.x > width + 50) {
1636
+      if (this.y > height - 100 || this.x < -50 || this.x > width + 50) {
1578
-                this.consecutiveAttacks++;
1637
+        this.consecutiveAttacks++
1579
-                
1638
+
1580
-                if (this.consecutiveAttacks < this.maxConsecutiveAttacks) {
1639
+        if (this.consecutiveAttacks < this.maxConsecutiveAttacks) {
1581
-                    // Come back from other side
1640
+          // Reset for another pass
1582
-                    this.x = -50;
1641
+          this.state = 'approaching'
1583
-                    this.state = 'approaching';
1642
+          this.x = random() < 0.5 ? -50 : width + 50
1584
-                    this.updateTarget();
1643
+          this.y = random(50, 150)
1585
-                } else {
1644
+          this.updateTarget()
1586
-                    this.state = 'retreating';
1645
+        } else {
1587
-                    this.attacking = false;
1646
+          this.state = 'retreating'
1588
-                }
1647
+          this.attacking = false
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);
1597
         }
1648
         }
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
+      }
1598
     }
1666
     }
1599
-    
1667
+  }
1600
-    executeGlidePattern() {
1668
+
1601
-        if (this.state === 'approaching') {
1669
+  executeCirclePattern () {
1602
-            // Glide in from top corner faster
1670
+    if (this.state === 'approaching') {
1603
-            this.x += 5;
1671
+      // Move to circle start position
1604
-            this.y += 2.5;
1672
+      let startX = this.circleCenter.x + cos(0) * this.circleRadius
1605
-            
1673
+      let startY = this.circleCenter.y + sin(0) * this.circleRadius
1606
-            if (this.y > height * 0.15) {
1674
+
1607
-                this.state = 'attacking';
1675
+      let dx = startX - this.x
1608
-                this.attacking = true;
1676
+      let dy = startY - this.y
1609
-                this.updateTarget();
1677
+
1610
-            }
1678
+      this.x += dx * 0.1
1611
-        } else if (this.state === 'attacking') {
1679
+      this.y += dy * 0.1
1612
-            // Glide toward target aggressively
1680
+
1613
-            let dx = this.targetX - this.x;
1681
+      if (abs(dx) < 20 && abs(dy) < 20) {
1614
-            let dy = this.targetY - this.y;
1682
+        this.state = 'attacking'
1615
-            let dist = sqrt(dx * dx + dy * dy);
1683
+        this.attacking = true
1616
-            
1684
+        this.circleAngle = 0
1617
-            if (dist > 10) {
1685
+      }
1618
-                this.x += (dx / dist) * 7; // Much faster glide
1686
+    } else if (this.state === 'attacking') {
1619
-                this.y += (dy / dist) * 7;
1687
+      // Circle around center FASTER
1620
-            }
1688
+      this.circleAngle += 0.08 // Faster circling
1621
-            
1689
+      this.x = this.circleCenter.x + cos(this.circleAngle) * this.circleRadius
1622
-            // Pass through and maybe attack again
1690
+      this.y = this.circleCenter.y + sin(this.circleAngle) * this.circleRadius
1623
-            if (this.y > height - 100 || this.x < -50 || this.x > width + 50) {
1691
+
1624
-                this.consecutiveAttacks++;
1692
+      // More frequent dives toward center
1625
-                
1693
+      if (frameCount % 60 === 0) {
1626
-                if (this.consecutiveAttacks < this.maxConsecutiveAttacks) {
1694
+        // Every second instead of every 2 seconds
1627
-                    // Reset for another pass
1695
+        this.circleRadius = max(30, this.circleRadius - 50)
1628
-                    this.state = 'approaching';
1696
+      } else {
1629
-                    this.x = random() < 0.5 ? -50 : width + 50;
1697
+        this.circleRadius = min(150, this.circleRadius + 2)
1630
-                    this.y = random(50, 150);
1698
+      }
1631
-                    this.updateTarget();
1699
+
1632
-                } else {
1700
+      // Complete circle faster
1633
-                    this.state = 'retreating';
1701
+      if (this.circleAngle > TWO_PI * 1.5) {
1634
-                    this.attacking = false;
1702
+        // 1.5 circles instead of 2
1635
-                }
1703
+        this.state = 'retreating'
1636
-            }
1704
+        this.attacking = false
1637
-        } else if (this.state === 'retreating') {
1705
+      }
1638
-            // Continue off screen
1706
+    } else if (this.state === 'retreating') {
1639
-            this.x += this.vx;
1707
+      // Fly away
1640
-            this.y += this.vy;
1708
+      this.y -= 7
1641
-            
1709
+
1642
-            // Reset
1710
+      if (this.y < -50) {
1643
-            if (this.y > height + 50 || this.x < -100 || this.x > width + 100) {
1711
+        this.state = 'approaching'
1644
-                this.state = 'approaching';
1712
+        this.attackDelay = random(150, 240)
1645
-                this.attackDelay = random(120, 180);
1713
+        this.x = random(width)
1646
-                this.x = random() < 0.5 ? -50 : width + 50;
1714
+      }
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
-        }
1654
     }
1715
     }
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
1655
     
1721
     
1656
-    executeCirclePattern() {
1722
+    // Check collision with spider
1657
-        if (this.state === 'approaching') {
1723
+    if (this.attacking && dist(this.x, this.y, spider.pos.x, spider.pos.y) < collisionDistance) {
1658
-            // Move to circle start position
1724
+        // Hit spider!
1659
-            let startX = this.circleCenter.x + cos(0) * this.circleRadius;
1725
+        if (gamePhase === 'DAWN') {
1660
-            let startY = this.circleCenter.y + sin(0) * this.circleRadius;
1726
+            // Calculate damage
1727
+            let damage = 20; // Base damage
1661
             
1728
             
1662
-            let dx = startX - this.x;
1729
+            // If spider has no stamina, GAME OVER!
1663
-            let dy = startY - this.y;
1730
+            if (jumpStamina <= 0) {
1731
+                triggerGameOver('Exhausted spider caught by bird!');
1732
+                return;
1733
+            }
1664
             
1734
             
1665
-            this.x += dx * 0.1;
1735
+            // Otherwise, reduce stamina
1666
-            this.y += dy * 0.1;
1736
+            jumpStamina = max(0, jumpStamina - damage);
1737
+            stats.birdHitsTaken++;
1667
             
1738
             
1668
-            if (abs(dx) < 20 && abs(dy) < 20) {
1739
+            // Knockback effect
1669
-                this.state = 'attacking';
1740
+            spider.vel.x = (spider.pos.x - this.x) * 0.3;
1670
-                this.attacking = true;
1741
+            spider.vel.y = -3;
1671
-                this.circleAngle = 0;
1742
+            spider.isAirborne = true;
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;
1678
             
1743
             
1679
-            // More frequent dives toward center
1744
+            // Red damage particles
1680
-            if (frameCount % 60 === 0) { // Every second instead of every 2 seconds
1745
+            for (let i = 0; i < 12; i++) {
1681
-                this.circleRadius = max(30, this.circleRadius - 50);
1746
+                let p = new Particle(spider.pos.x, spider.pos.y);
1682
-            } else {
1747
+                p.color = color(255, 50, 50);
1683
-                this.circleRadius = min(150, this.circleRadius + 2);
1748
+                p.vel = createVector(random(-4, 4), random(-4, 1));
1749
+                p.size = random(4, 8);
1750
+                particles.push(p);
1684
             }
1751
             }
1685
             
1752
             
1686
-            // Complete circle faster
1753
+            // Screen shake effect
1687
-            if (this.circleAngle > TWO_PI * 1.5) { // 1.5 circles instead of 2
1754
+            if (typeof screenShake !== 'undefined') {
1688
-                this.state = 'retreating';
1755
+                screenShake = 10;
1689
-                this.attacking = false;
1690
             }
1756
             }
1691
-        } else if (this.state === 'retreating') {
1692
-            // Fly away
1693
-            this.y -= 7;
1694
             
1757
             
1695
-            if (this.y < -50) {
1758
+            // Warning notifications - but limited to prevent spam
1696
-                this.state = 'approaching';
1759
+            if (notifications.length < 3) { // Limit notifications
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
1739
                 if (jumpStamina <= 20) {
1760
                 if (jumpStamina <= 20) {
1740
                     notifications.push(new Notification("CRITICAL STAMINA!", color(255, 50, 50)));
1761
                     notifications.push(new Notification("CRITICAL STAMINA!", color(255, 50, 50)));
1741
                 } else if (jumpStamina <= 40) {
1762
                 } else if (jumpStamina <= 40) {
1742
                     notifications.push(new Notification("Low stamina - find cover!", color(255, 150, 50)));
1763
                     notifications.push(new Notification("Low stamina - find cover!", color(255, 150, 50)));
1743
                 }
1764
                 }
1744
             }
1765
             }
1745
-            
1746
-            // Bird bounces off
1747
-            this.state = 'retreating';
1748
-            this.attacking = false;
1749
         }
1766
         }
1767
+        
1768
+        // Bird bounces off
1769
+        this.state = 'retreating';
1770
+        this.attacking = false;
1771
+    }
1750
 
1772
 
1751
     // Check collision with web strands
1773
     // Check collision with web strands
1752
     if (this.attacking) {
1774
     if (this.attacking) {
js/game.jsmodified
@@ -18,6 +18,7 @@ let gameOver = false
18
 let gameOverTimer = 0
18
 let gameOverTimer = 0
19
 let deathReason = ''
19
 let deathReason = ''
20
 let finalScore = 0
20
 let finalScore = 0
21
+let screenShake = 0
21
 
22
 
22
 // Resources
23
 // Resources
23
 let webSilk = 100
24
 let webSilk = 100
@@ -320,27 +321,50 @@ class Notification {
320
   constructor (text, color) {
321
   constructor (text, color) {
321
     this.text = text
322
     this.text = text
322
     this.color = color
323
     this.color = color
323
-    this.y = height * 0.3
324
-    this.alpha = 255
325
     this.lifetime = 180 // 3 seconds
324
     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
+    }
326
   }
338
   }
327
 
339
 
328
   update () {
340
   update () {
329
     this.lifetime--
341
     this.lifetime--
342
+
343
+    // Fade out in the last second
330
     if (this.lifetime < 60) {
344
     if (this.lifetime < 60) {
331
       this.alpha = map(this.lifetime, 0, 60, 0, 255)
345
       this.alpha = map(this.lifetime, 0, 60, 0, 255)
332
     }
346
     }
333
-    this.y -= 0.5 // Slowly rise
347
+
348
+    // Slowly rise
349
+    this.y -= 0.3 // Slower rise to maintain readability
334
   }
350
   }
335
 
351
 
336
   display () {
352
   display () {
337
     push()
353
     push()
338
     textAlign(CENTER)
354
     textAlign(CENTER)
339
-    textSize(24)
355
+
340
-    strokeWeight(4)
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)
341
     stroke(0, 0, 0, this.alpha)
365
     stroke(0, 0, 0, this.alpha)
342
     fill(red(this.color), green(this.color), blue(this.color), this.alpha)
366
     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)
344
     pop()
368
     pop()
345
   }
369
   }
346
 
370
 
@@ -662,45 +686,36 @@ function draw () {
662
     gamePhase = 'DAWN'
686
     gamePhase = 'DAWN'
663
     phaseTimer = 0
687
     phaseTimer = 0
664
 
688
 
665
-    // ENHANCED: Calculate dawn stamina with CLEAR BONUS DISPLAY
689
+    // Calculate dawn stamina with CLEAR BONUS DISPLAY
666
     let baseStamina = 30
690
     let baseStamina = 30
667
     let flyBonus = fliesMunchedLastNight * 10
691
     let flyBonus = fliesMunchedLastNight * 10
668
-    staminaBonus = flyBonus // Store for display
692
+    staminaBonus = flyBonus
669
 
693
 
670
     maxJumpStamina = baseStamina + flyBonus
694
     maxJumpStamina = baseStamina + flyBonus
671
-    maxJumpStamina = min(maxJumpStamina, 200) // Cap at 200 instead of 150
695
+    maxJumpStamina = min(maxJumpStamina, 200)
672
     jumpStamina = maxJumpStamina
696
     jumpStamina = maxJumpStamina
673
 
697
 
674
-    // Clear notification showing stamina calculation
698
+    // IMPROVED: Combine notifications to reduce overlap
675
-    notifications.push(
699
+    let staminaMessage = `Dawn: ${maxJumpStamina} stamina (${baseStamina} + ${flyBonus} from ${fliesMunchedLastNight} flies)`
676
-      new Notification(
700
+    let warningMessage = ''
677
-        `Dawn Stamina: ${baseStamina} base + ${flyBonus} (${fliesMunchedLastNight} flies × 10) = ${maxJumpStamina}`,
678
-        color(255, 200, 100)
679
-      )
680
-    )
681
 
701
 
682
-    // Warning notifications based on stamina level
683
     if (maxJumpStamina <= 50) {
702
     if (maxJumpStamina <= 50) {
703
+      warningMessage = ' ⚠️ DANGER!'
684
       notifications.push(
704
       notifications.push(
685
-        new Notification(
705
+        new Notification(staminaMessage + warningMessage, color(255, 50, 50))
686
-          '⚠️ DANGER: Very low stamina! Eat more flies next night!',
687
-          color(255, 50, 50)
688
-        )
689
       )
706
       )
690
     } else if (maxJumpStamina <= 80) {
707
     } else if (maxJumpStamina <= 80) {
708
+      warningMessage = ' - Low stamina!'
691
       notifications.push(
709
       notifications.push(
692
-        new Notification(
710
+        new Notification(staminaMessage + warningMessage, color(255, 150, 50))
693
-          'Warning: Low stamina for dawn survival',
694
-          color(255, 150, 50)
695
-        )
696
       )
711
       )
697
     } else if (maxJumpStamina >= 150) {
712
     } else if (maxJumpStamina >= 150) {
713
+      warningMessage = ' - Well fed!'
698
       notifications.push(
714
       notifications.push(
699
-        new Notification(
715
+        new Notification(staminaMessage + warningMessage, color(100, 255, 100))
700
-          'Excellent stamina! Well fed spider!',
701
-          color(100, 255, 100)
702
-        )
703
       )
716
       )
717
+    } else {
718
+      notifications.push(new Notification(staminaMessage, color(255, 200, 100)))
704
     }
719
     }
705
 
720
 
706
     // Spawn birds
721
     // Spawn birds