@@ -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) { |