fixes, cleanup
- SHA
6237a51a44013f993f3d0de5b1ea02df81a80afe- Parents
-
827e77f - Tree
d2b83b9
6237a51
6237a51a44013f993f3d0de5b1ea02df81a80afe827e77f
d2b83b9| Status | File | + | - |
|---|---|---|---|
| M |
deploy.sh
|
0 | 1 |
| M |
index.html
|
1 | 1 |
| M |
js/entities.js
|
331 | 173 |
| M |
js/game.js
|
179 | 129 |
deploy.shmodified@@ -54,7 +54,6 @@ sudo chown -R root:root "$RELEASES/$STAMP" | ||
| 54 | 54 | sudo find "$RELEASES/$STAMP" -type d -exec chmod 0755 {} + |
| 55 | 55 | sudo find "$RELEASES/$STAMP" -type f -exec chmod 0644 {} + |
| 56 | 56 | |
| 57 | -# If the nginx user is different on your system, adjust here | |
| 58 | 57 | NGINX_USER="nginx" |
| 59 | 58 | if id "$NGINX_USER" >/dev/null 2>&1; then |
| 60 | 59 | sudo -u "$NGINX_USER" test -r "$RELEASES/$STAMP/index.html" || { echo "✗ nginx user cannot read index.html"; exit 1; } |
index.htmlmodified@@ -13,7 +13,7 @@ | ||
| 13 | 13 | Click to jump • Space to spin web • Shift to munch!<br> |
| 14 | 14 | Web Strands: <span id="strand-count">0</span><br> |
| 15 | 15 | Flies Caught: <span id="flies-caught">0</span> | Munched: <span id="flies-munched">0</span><br> |
| 16 | - Total Score: <span id="total-score">0</span> | |
| 16 | + Points: <span id="player-points">0</span> | Total Score: <span id="total-score">0</span> | |
| 17 | 17 | </div> |
| 18 | 18 | <div id="phase-indicator"> |
| 19 | 19 | <span id="phase">DUSK</span><br> |
js/entities.jsmodified@@ -22,16 +22,21 @@ class Spider { | ||
| 22 | 22 | |
| 23 | 23 | // DAWN PHASE: Check and consume stamina |
| 24 | 24 | if (gamePhase === 'DAWN') { |
| 25 | + // FIX: Only check if we ACTUALLY don't have enough stamina | |
| 25 | 26 | if (jumpStamina < jumpCost) { |
| 26 | 27 | isExhausted = true |
| 28 | + // Only show notification if we're truly out | |
| 29 | + if (jumpStamina < 5 && notifications.length < 3) { | |
| 30 | + notifications.push( | |
| 31 | + new Notification('NO STAMINA!', color(255, 50, 50)) | |
| 32 | + ) | |
| 33 | + } | |
| 27 | 34 | return |
| 28 | 35 | } |
| 29 | 36 | jumpStamina -= jumpCost |
| 30 | 37 | stats.totalJumps++ |
| 31 | 38 | // Delay stamina regen after each jump during DAWN |
| 32 | - if (gamePhase === 'DAWN') { | |
| 33 | - staminaRegenCooldown = 60 // 1s at 60fps | |
| 34 | - } | |
| 39 | + staminaRegenCooldown = 60 // 1s at 60fps | |
| 35 | 40 | } |
| 36 | 41 | |
| 37 | 42 | // PHASE 4B: Track wind jumps |
@@ -119,15 +124,57 @@ class Spider { | ||
| 119 | 124 | if (this.munchCooldown > 0) return |
| 120 | 125 | |
| 121 | 126 | isMunching = true |
| 122 | - this.munchCooldown = 30 | |
| 127 | + this.munchCooldown = this.munchCooldownMax || 30 // Use upgrade value if available | |
| 123 | 128 | |
| 124 | 129 | for (let i = flies.length - 1; i >= 0; i--) { |
| 125 | 130 | let fly = flies[i] |
| 126 | 131 | let d = dist(this.pos.x, this.pos.y, fly.pos.x, fly.pos.y) |
| 127 | 132 | if (d < this.munchRadius) { |
| 128 | 133 | fliesMunched++ |
| 134 | + stats.fliesMunchedInCurrentNight++ // Track for achievements | |
| 135 | + | |
| 136 | + // POINTS: Award points for munching | |
| 137 | + let munchPoints = 4 // Base points for munch | |
| 138 | + if (fly.type === 'golden') munchPoints = 8 | |
| 139 | + if (fly.type === 'queen') munchPoints = 10 | |
| 140 | + if (fly.type === 'moth') munchPoints = 6 | |
| 141 | + playerPoints += munchPoints | |
| 142 | + totalFliesCaught++ // Add to lifetime counter | |
| 143 | + | |
| 129 | 144 | webSilk = min(webSilk + 15, maxWebSilk) |
| 130 | 145 | |
| 146 | + // PHASE 3: Metabolize upgrade effect | |
| 147 | + if (upgrades.metabolize && upgrades.metabolize.level > 0) { | |
| 148 | + // Heal nearby broken strands | |
| 149 | + for (let strand of webStrands) { | |
| 150 | + if (strand.broken) { | |
| 151 | + let distToStrand = Infinity | |
| 152 | + if (strand.path && strand.path.length > 0) { | |
| 153 | + for (let point of strand.path) { | |
| 154 | + let d = dist(this.pos.x, this.pos.y, point.x, point.y) | |
| 155 | + if (d < distToStrand) distToStrand = d | |
| 156 | + } | |
| 157 | + } | |
| 158 | + | |
| 159 | + if (distToStrand < 100) { | |
| 160 | + strand.broken = false | |
| 161 | + strand.strength = 0.5 // Heal to half strength | |
| 162 | + | |
| 163 | + // Green healing particles | |
| 164 | + for (let j = 0; j < 5; j++) { | |
| 165 | + let p = new Particle(this.pos.x, this.pos.y) | |
| 166 | + p.color = color(100, 255, 100) | |
| 167 | + p.vel = createVector(random(-2, 2), random(-2, 2)) | |
| 168 | + p.size = 3 | |
| 169 | + particles.push(p) | |
| 170 | + } | |
| 171 | + | |
| 172 | + break // Only heal one strand per munch | |
| 173 | + } | |
| 174 | + } | |
| 175 | + } | |
| 176 | + } | |
| 177 | + | |
| 131 | 178 | for (let j = 0; j < 12; j++) { |
| 132 | 179 | let p = new Particle(fly.pos.x, fly.pos.y) |
| 133 | 180 | p.color = color(255, random(100, 255), 0) |
@@ -202,34 +249,62 @@ class Spider { | ||
| 202 | 249 | let branchStart = Math.min(branch.startX, branch.endX) |
| 203 | 250 | let branchEnd = Math.max(branch.startX, branch.endX) |
| 204 | 251 | |
| 205 | - // Since the branch angle is very small (0.05 radians ≈ 3 degrees), | |
| 206 | - // we can use a simpler approximation | |
| 207 | - if (this.pos.x >= branchStart - 10 && this.pos.x <= branchEnd + 10) { | |
| 252 | + // FIX: Extend collision detection beyond visual tip | |
| 253 | + // The visual branch ends at branchEnd, but we need collision slightly beyond | |
| 254 | + let collisionPadding = 15 // Extra collision at the tip | |
| 255 | + | |
| 256 | + if ( | |
| 257 | + this.pos.x >= branchStart - 10 && | |
| 258 | + this.pos.x <= branchEnd + collisionPadding | |
| 259 | + ) { | |
| 208 | 260 | // Calculate position along branch (0 to 1) |
| 261 | + // FIX: Clamp t to ensure we handle tip properly | |
| 209 | 262 | let t = (this.pos.x - branchStart) / (branchEnd - branchStart) |
| 210 | - t = constrain(t, 0, 1) | |
| 263 | + | |
| 264 | + // Allow t to go slightly beyond 1.0 for tip collision | |
| 265 | + if (this.pos.x > branchEnd) { | |
| 266 | + t = 1.0 // At the tip, use tip thickness | |
| 267 | + } else { | |
| 268 | + t = constrain(t, 0, 1) | |
| 269 | + } | |
| 211 | 270 | |
| 212 | 271 | // Branch visual thickness tapers from full at start to 35% at end |
| 213 | - // This matches exactly how it's drawn in the bezier curves | |
| 214 | - let branchTopThickness = lerp( | |
| 215 | - branch.thickness * 0.9, | |
| 216 | - branch.thickness * 0.35, | |
| 217 | - t | |
| 218 | - ) | |
| 272 | + let branchTopThickness | |
| 273 | + if (t >= 1.0) { | |
| 274 | + // At the very tip, maintain minimum collision thickness | |
| 275 | + branchTopThickness = branch.thickness * 0.35 | |
| 276 | + } else { | |
| 277 | + branchTopThickness = lerp( | |
| 278 | + branch.thickness * 0.9, | |
| 279 | + branch.thickness * 0.35, | |
| 280 | + t | |
| 281 | + ) | |
| 282 | + } | |
| 219 | 283 | |
| 220 | 284 | // The branch is drawn centered at branch.y |
| 221 | - // With small angle approximation: the top of the branch is at | |
| 222 | 285 | let branchSurfaceY = branch.y - branchTopThickness |
| 223 | 286 | |
| 224 | - // Add slight angle correction (for small angles, tan ≈ sin ≈ angle in radians) | |
| 225 | - let angleCorrection = (this.pos.x - branchStart) * branch.angle | |
| 287 | + // FIX: Correct angle calculation based on branch side | |
| 288 | + let angleCorrection | |
| 289 | + if (branch.side === 'right') { | |
| 290 | + // Right branch slopes down to the left (negative angle) | |
| 291 | + // Use distance from the end point (which is on the left for right branches) | |
| 292 | + angleCorrection = -(this.pos.x - branchEnd) * abs(branch.angle) | |
| 293 | + } else { | |
| 294 | + // Left branch slopes down to the right (positive angle) | |
| 295 | + // Use distance from the start point | |
| 296 | + angleCorrection = (this.pos.x - branchStart) * abs(branch.angle) | |
| 297 | + } | |
| 226 | 298 | branchSurfaceY += angleCorrection |
| 227 | 299 | |
| 228 | 300 | // Check if spider is crossing the branch from above |
| 229 | 301 | let prevY = this.pos.y - this.vel.y |
| 230 | 302 | |
| 303 | + // FIX: More generous collision detection at the tip | |
| 304 | + let collisionBuffer = t >= 0.9 ? 5 : 0 // Extra buffer near tip | |
| 305 | + | |
| 231 | 306 | if ( |
| 232 | - prevY <= branchSurfaceY && // Was above | |
| 307 | + prevY <= branchSurfaceY + collisionBuffer && // Was above (with buffer) | |
| 233 | 308 | this.pos.y + this.radius >= branchSurfaceY && // Now at or below |
| 234 | 309 | this.pos.y < branch.y + branch.thickness |
| 235 | 310 | ) { |
@@ -350,132 +425,148 @@ class Spider { | ||
| 350 | 425 | this.land() |
| 351 | 426 | } |
| 352 | 427 | |
| 353 | -land() { | |
| 354 | - this.vel.mult(0); | |
| 355 | - this.isAirborne = false; | |
| 356 | - this.canJump = true; | |
| 357 | - | |
| 358 | - // FIX: Check if we're actually landing on something valid | |
| 359 | - let landedOnSomething = false; | |
| 360 | - let landingPoint = null; // Store where we're landing for anchor | |
| 361 | - let landingObstacle = null; // NEW: Track which obstacle we're landing on | |
| 362 | - | |
| 363 | - // Check if on ground | |
| 364 | - if (this.pos.y >= height - this.radius - 5) { | |
| 365 | - landedOnSomething = true; | |
| 366 | - landingPoint = createVector(this.pos.x, height); | |
| 367 | - } | |
| 428 | + land () { | |
| 429 | + this.vel.mult(0) | |
| 430 | + this.isAirborne = false | |
| 431 | + this.canJump = true | |
| 368 | 432 | |
| 369 | - // Check if on an obstacle | |
| 370 | - if (!landedOnSomething) { | |
| 371 | - for (let obstacle of obstacles) { | |
| 372 | - if (this.checkObstacleCollision(obstacle)) { | |
| 373 | - landedOnSomething = true; | |
| 374 | - landingObstacle = obstacle; // NEW: Store the obstacle | |
| 375 | - // Calculate edge point for anchor | |
| 376 | - let angle = atan2(this.pos.y - obstacle.y, this.pos.x - obstacle.x); | |
| 377 | - landingPoint = createVector( | |
| 378 | - obstacle.x + cos(angle) * obstacle.radius, | |
| 379 | - obstacle.y + sin(angle) * obstacle.radius | |
| 380 | - ); | |
| 381 | - break; | |
| 433 | + // FIX: Check if we're actually landing on something valid | |
| 434 | + let landedOnSomething = false | |
| 435 | + let landingPoint = null // Store where we're landing for anchor | |
| 436 | + let landingObstacle = null // NEW: Track which obstacle we're landing on | |
| 437 | + | |
| 438 | + // Check if on ground | |
| 439 | + if (this.pos.y >= height - this.radius - 5) { | |
| 440 | + landedOnSomething = true | |
| 441 | + landingPoint = createVector(this.pos.x, height) | |
| 442 | + } | |
| 443 | + | |
| 444 | + // Check if on an obstacle | |
| 445 | + if (!landedOnSomething) { | |
| 446 | + for (let obstacle of obstacles) { | |
| 447 | + if (this.checkObstacleCollision(obstacle)) { | |
| 448 | + landedOnSomething = true | |
| 449 | + landingObstacle = obstacle // NEW: Store the obstacle | |
| 450 | + // Calculate edge point for anchor | |
| 451 | + let angle = atan2(this.pos.y - obstacle.y, this.pos.x - obstacle.x) | |
| 452 | + landingPoint = createVector( | |
| 453 | + obstacle.x + cos(angle) * obstacle.radius, | |
| 454 | + obstacle.y + sin(angle) * obstacle.radius | |
| 455 | + ) | |
| 456 | + break | |
| 457 | + } | |
| 382 | 458 | } |
| 383 | 459 | } |
| 384 | - } | |
| 385 | 460 | |
| 386 | - // Check if on a web strand | |
| 387 | - if (!landedOnSomething) { | |
| 388 | - for (let strand of webStrands) { | |
| 389 | - if (strand !== currentStrand && !strand.broken && this.checkStrandCollision(strand)) { | |
| 390 | - landedOnSomething = true; | |
| 391 | - // For web strands, use spider position as anchor | |
| 392 | - landingPoint = this.pos.copy(); | |
| 393 | - break; | |
| 461 | + // Check if on a web strand | |
| 462 | + if (!landedOnSomething) { | |
| 463 | + for (let strand of webStrands) { | |
| 464 | + if ( | |
| 465 | + strand !== currentStrand && | |
| 466 | + !strand.broken && | |
| 467 | + this.checkStrandCollision(strand) | |
| 468 | + ) { | |
| 469 | + landedOnSomething = true | |
| 470 | + // For web strands, use spider position as anchor | |
| 471 | + landingPoint = this.pos.copy() | |
| 472 | + break | |
| 473 | + } | |
| 394 | 474 | } |
| 395 | 475 | } |
| 396 | - } | |
| 397 | 476 | |
| 398 | - // Check if on home branch | |
| 399 | - if (!landedOnSomething && window.homeBranch) { | |
| 400 | - let branch = window.homeBranch; | |
| 401 | - let branchStart = Math.min(branch.startX, branch.endX); | |
| 402 | - let branchEnd = Math.max(branch.startX, branch.endX); | |
| 403 | - | |
| 404 | - if (this.pos.x >= branchStart - 10 && this.pos.x <= branchEnd + 10) { | |
| 405 | - let t = (this.pos.x - branchStart) / (branchEnd - branchStart); | |
| 406 | - t = constrain(t, 0, 1); | |
| 407 | - let branchTopThickness = lerp(branch.thickness * 0.9, branch.thickness * 0.35, t); | |
| 408 | - let branchSurfaceY = branch.y - branchTopThickness; | |
| 409 | - let angleCorrection = (this.pos.x - branchStart) * branch.angle; | |
| 410 | - branchSurfaceY += angleCorrection; | |
| 411 | - | |
| 412 | - if (abs(this.pos.y - branchSurfaceY) < this.radius + 10) { | |
| 413 | - landedOnSomething = true; | |
| 414 | - landingPoint = createVector(this.pos.x, branchSurfaceY); | |
| 477 | + // Check if on home branch | |
| 478 | + if (!landedOnSomething && window.homeBranch) { | |
| 479 | + let branch = window.homeBranch | |
| 480 | + let branchStart = Math.min(branch.startX, branch.endX) | |
| 481 | + let branchEnd = Math.max(branch.startX, branch.endX) | |
| 482 | + | |
| 483 | + if (this.pos.x >= branchStart - 10 && this.pos.x <= branchEnd + 10) { | |
| 484 | + let t = (this.pos.x - branchStart) / (branchEnd - branchStart) | |
| 485 | + t = constrain(t, 0, 1) | |
| 486 | + let branchTopThickness = lerp( | |
| 487 | + branch.thickness * 0.9, | |
| 488 | + branch.thickness * 0.35, | |
| 489 | + t | |
| 490 | + ) | |
| 491 | + let branchSurfaceY = branch.y - branchTopThickness | |
| 492 | + let angleCorrection = (this.pos.x - branchStart) * branch.angle | |
| 493 | + branchSurfaceY += angleCorrection | |
| 494 | + | |
| 495 | + if (abs(this.pos.y - branchSurfaceY) < this.radius + 10) { | |
| 496 | + landedOnSomething = true | |
| 497 | + landingPoint = createVector(this.pos.x, branchSurfaceY) | |
| 498 | + } | |
| 415 | 499 | } |
| 416 | 500 | } |
| 417 | - } | |
| 418 | 501 | |
| 419 | - // FIX: If we're deploying web but didn't land on anything valid, destroy the web | |
| 420 | - if (currentStrand && isDeployingWeb && (spacePressed || touchHolding)) { | |
| 421 | - if (landedOnSomething && landingPoint) { | |
| 422 | - // Valid landing - finalize the web at the landing point | |
| 423 | - currentStrand.end = landingPoint.copy(); // Use edge point, not spider center | |
| 424 | - | |
| 425 | - // NEW: Track obstacle attachment for the end point | |
| 426 | - if (landingObstacle) { | |
| 427 | - currentStrand.endObstacle = landingObstacle; | |
| 428 | - currentStrand.endAngle = atan2( | |
| 429 | - landingPoint.y - landingObstacle.y, | |
| 430 | - landingPoint.x - landingObstacle.x | |
| 431 | - ); | |
| 432 | - } | |
| 433 | - | |
| 434 | - if (!currentStrand.path || currentStrand.path.length === 0) { | |
| 435 | - currentStrand.path = [landingPoint.copy()]; | |
| 436 | - } else { | |
| 437 | - currentStrand.path.push(landingPoint.copy()); | |
| 438 | - } | |
| 439 | - | |
| 440 | - let newNode = new WebNode(landingPoint.x, landingPoint.y); | |
| 441 | - // NEW: Track node attachment | |
| 442 | - if (landingObstacle) { | |
| 443 | - newNode.attachedObstacle = landingObstacle; | |
| 444 | - newNode.attachmentAngle = currentStrand.endAngle; | |
| 445 | - } | |
| 446 | - webNodes.push(newNode); | |
| 502 | + // FIX: If we're deploying web but didn't land on anything valid, destroy the web | |
| 503 | + if (currentStrand && isDeployingWeb && (spacePressed || touchHolding)) { | |
| 504 | + if (landedOnSomething && landingPoint) { | |
| 505 | + // Valid landing - finalize the web at the landing point | |
| 506 | + currentStrand.end = landingPoint.copy() // Use edge point, not spider center | |
| 447 | 507 | |
| 448 | - // Update last anchor for next web | |
| 449 | - this.lastAnchorPoint = landingPoint.copy(); | |
| 450 | - } else { | |
| 451 | - // Invalid landing in mid-air - destroy the web! | |
| 452 | - if (webStrands.length > 0 && webStrands[webStrands.length - 1] === currentStrand) { | |
| 453 | - webStrands.pop(); // Remove the invalid strand | |
| 508 | + // POINTS: Award 1 point for successful web strand | |
| 509 | + playerPoints += 1 | |
| 510 | + stats.strandsCreated++ // Track for stats | |
| 454 | 511 | |
| 455 | - // Create poof particles | |
| 456 | - for (let i = 0; i < 8; i++) { | |
| 457 | - let p = new Particle(this.pos.x, this.pos.y); | |
| 458 | - p.color = color(255, 255, 255, 150); | |
| 459 | - p.vel = createVector(random(-3, 3), random(-3, 3)); | |
| 460 | - p.size = 4; | |
| 461 | - particles.push(p); | |
| 512 | + // NEW: Track obstacle attachment for the end point | |
| 513 | + if (landingObstacle) { | |
| 514 | + currentStrand.endObstacle = landingObstacle | |
| 515 | + currentStrand.endAngle = atan2( | |
| 516 | + landingPoint.y - landingObstacle.y, | |
| 517 | + landingPoint.x - landingObstacle.x | |
| 518 | + ) | |
| 462 | 519 | } |
| 463 | 520 | |
| 464 | - // Notification | |
| 465 | - if (notifications.length < 3) { | |
| 466 | - notifications.push(new Notification("Web needs anchor point!", color(255, 150, 150))); | |
| 521 | + if (!currentStrand.path || currentStrand.path.length === 0) { | |
| 522 | + currentStrand.path = [landingPoint.copy()] | |
| 523 | + } else { | |
| 524 | + currentStrand.path.push(landingPoint.copy()) | |
| 525 | + } | |
| 526 | + | |
| 527 | + let newNode = new WebNode(landingPoint.x, landingPoint.y) | |
| 528 | + // NEW: Track node attachment | |
| 529 | + if (landingObstacle) { | |
| 530 | + newNode.attachedObstacle = landingObstacle | |
| 531 | + newNode.attachmentAngle = currentStrand.endAngle | |
| 532 | + } | |
| 533 | + webNodes.push(newNode) | |
| 534 | + | |
| 535 | + // Update last anchor for next web | |
| 536 | + this.lastAnchorPoint = landingPoint.copy() | |
| 537 | + } else { | |
| 538 | + // Invalid landing in mid-air - destroy the web! | |
| 539 | + if ( | |
| 540 | + webStrands.length > 0 && | |
| 541 | + webStrands[webStrands.length - 1] === currentStrand | |
| 542 | + ) { | |
| 543 | + webStrands.pop() // Remove the invalid strand | |
| 544 | + | |
| 545 | + // Create poof particles | |
| 546 | + for (let i = 0; i < 8; i++) { | |
| 547 | + let p = new Particle(this.pos.x, this.pos.y) | |
| 548 | + p.color = color(255, 255, 255, 150) | |
| 549 | + p.vel = createVector(random(-3, 3), random(-3, 3)) | |
| 550 | + p.size = 4 | |
| 551 | + particles.push(p) | |
| 552 | + } | |
| 553 | + | |
| 554 | + // Notification | |
| 555 | + if (notifications.length < 3) { | |
| 556 | + notifications.push( | |
| 557 | + new Notification('Web needs anchor point!', color(255, 150, 150)) | |
| 558 | + ) | |
| 559 | + } | |
| 467 | 560 | } |
| 468 | 561 | } |
| 562 | + } else if (landedOnSomething && landingPoint) { | |
| 563 | + // Update last anchor point even when not deploying web | |
| 564 | + this.lastAnchorPoint = landingPoint.copy() | |
| 469 | 565 | } |
| 470 | - } else if (landedOnSomething && landingPoint) { | |
| 471 | - // Update last anchor point even when not deploying web | |
| 472 | - this.lastAnchorPoint = landingPoint.copy(); | |
| 473 | - } | |
| 474 | - | |
| 475 | - currentStrand = null; | |
| 476 | - isDeployingWeb = false; | |
| 477 | -} | |
| 478 | 566 | |
| 567 | + currentStrand = null | |
| 568 | + isDeployingWeb = false | |
| 569 | + } | |
| 479 | 570 | |
| 480 | 571 | display () { |
| 481 | 572 | push() |
@@ -552,7 +643,6 @@ class Fly { | ||
| 552 | 643 | |
| 553 | 644 | update () { |
| 554 | 645 | if (this.stuck) { |
| 555 | - // If stuck, check if we need to move with a drifting web | |
| 556 | 646 | this.updatePositionOnWeb() |
| 557 | 647 | return |
| 558 | 648 | } |
@@ -562,9 +652,17 @@ class Fly { | ||
| 562 | 652 | if (this.vel.mag() < 0.1) { |
| 563 | 653 | this.stuck = true |
| 564 | 654 | fliesCaught++ |
| 655 | + totalFliesCaught++ // Add to lifetime counter | |
| 656 | + | |
| 657 | + // POINTS: Award points for catching fly | |
| 658 | + let catchPoints = 2 // Base points for catch | |
| 659 | + if (this.type === 'golden') catchPoints = 4 | |
| 660 | + if (this.type === 'queen') catchPoints = 5 | |
| 661 | + if (this.type === 'moth') catchPoints = 3 | |
| 662 | + playerPoints += catchPoints | |
| 663 | + | |
| 565 | 664 | webSilk = min(webSilk + 5, maxWebSilk) |
| 566 | 665 | } |
| 567 | - // While caught but not yet stuck, also follow the web | |
| 568 | 666 | this.updatePositionOnWeb() |
| 569 | 667 | return |
| 570 | 668 | } |
@@ -759,7 +857,29 @@ class Fly { | ||
| 759 | 857 | ellipse(0, 0, 20) |
| 760 | 858 | } |
| 761 | 859 | |
| 762 | - if (gamePhase === 'NIGHT') { | |
| 860 | + // ENHANCED: Special golden fly glow | |
| 861 | + if (this.type === 'golden') { | |
| 862 | + // Multiple layers of golden glow for visibility | |
| 863 | + noStroke() | |
| 864 | + // Outermost glow - very faint but wide | |
| 865 | + fill(255, 215, 0, 15) | |
| 866 | + ellipse(0, 0, 80) | |
| 867 | + // Mid glow | |
| 868 | + fill(255, 200, 0, 25) | |
| 869 | + ellipse(0, 0, 60) | |
| 870 | + // Inner glow | |
| 871 | + fill(255, 185, 0, 40) | |
| 872 | + ellipse(0, 0, 40) | |
| 873 | + // Core glow | |
| 874 | + fill(255, 170, 0, 60) | |
| 875 | + ellipse(0, 0, 25) | |
| 876 | + | |
| 877 | + // Pulsing effect | |
| 878 | + let pulse = sin(frameCount * 0.1) * 0.3 + 0.7 | |
| 879 | + fill(255, 215, 0, 80 * pulse) | |
| 880 | + ellipse(0, 0, 20) | |
| 881 | + } else if (gamePhase === 'NIGHT') { | |
| 882 | + // Regular firefly glow | |
| 763 | 883 | noStroke() |
| 764 | 884 | fill(255, 255, 150, this.glowIntensity * 0.3) |
| 765 | 885 | ellipse(0, 0, 30) |
@@ -767,8 +887,21 @@ class Fly { | ||
| 767 | 887 | ellipse(0, 0, 20) |
| 768 | 888 | } |
| 769 | 889 | |
| 770 | - fill(30) | |
| 771 | - stroke(0) | |
| 890 | + // Body color based on type | |
| 891 | + if (this.type === 'golden') { | |
| 892 | + fill(255, 215, 0) // Gold body | |
| 893 | + stroke(200, 150, 0) | |
| 894 | + } else if (this.type === 'moth') { | |
| 895 | + fill(60, 40, 30) // Brown moth | |
| 896 | + stroke(40, 20, 10) | |
| 897 | + } else if (this.type === 'queen') { | |
| 898 | + fill(150, 50, 200) // Purple queen | |
| 899 | + stroke(100, 0, 150) | |
| 900 | + } else { | |
| 901 | + fill(30) // Regular black | |
| 902 | + stroke(0) | |
| 903 | + } | |
| 904 | + | |
| 772 | 905 | strokeWeight(0.5) |
| 773 | 906 | ellipse(0, 0, this.radius * 2) |
| 774 | 907 | |
@@ -778,13 +911,37 @@ class Fly { | ||
| 778 | 911 | this.wingPhase += wingSpeed |
| 779 | 912 | let wingSpread = sin(this.wingPhase) * 5 |
| 780 | 913 | |
| 781 | - fill(255, 255, 255, 150) | |
| 914 | + // Wing color based on type | |
| 915 | + if (this.type === 'golden') { | |
| 916 | + fill(255, 240, 150, 200) // Golden translucent wings | |
| 917 | + } else if (this.type === 'moth') { | |
| 918 | + fill(120, 100, 80, 150) // Brown moth wings | |
| 919 | + } else if (this.type === 'queen') { | |
| 920 | + fill(200, 150, 255, 180) // Purple queen wings | |
| 921 | + } else { | |
| 922 | + fill(255, 255, 255, 150) // Regular white wings | |
| 923 | + } | |
| 924 | + | |
| 782 | 925 | noStroke() |
| 783 | 926 | ellipse(-wingSpread, 0, 6, 4) |
| 784 | 927 | ellipse(wingSpread, 0, 6, 4) |
| 785 | 928 | } |
| 786 | 929 | |
| 787 | - if (gamePhase === 'NIGHT') { | |
| 930 | + // Special markings for special types | |
| 931 | + if (this.type === 'golden') { | |
| 932 | + // Golden sparkle on body | |
| 933 | + fill(255, 255, 200) | |
| 934 | + noStroke() | |
| 935 | + ellipse(0, 0, 2) | |
| 936 | + } else if (this.type === 'queen') { | |
| 937 | + // Crown marking | |
| 938 | + stroke(255, 215, 0) | |
| 939 | + strokeWeight(1) | |
| 940 | + line(-2, -3, -1, -5) | |
| 941 | + line(0, -3, 0, -5) | |
| 942 | + line(2, -3, 1, -5) | |
| 943 | + } else if (gamePhase === 'NIGHT') { | |
| 944 | + // Regular glow abdomen | |
| 788 | 945 | fill(255, 255, 100, this.glowIntensity) |
| 789 | 946 | noStroke() |
| 790 | 947 | ellipse(0, 2, 3) |
@@ -911,50 +1068,49 @@ class Obstacle { | ||
| 911 | 1068 | } |
| 912 | 1069 | } |
| 913 | 1070 | |
| 914 | -updateAttachedStrands() { | |
| 915 | - // Update web strands that are connected to this obstacle | |
| 916 | - for (let strand of webStrands) { | |
| 917 | - if (!strand || strand.broken) continue; | |
| 918 | - | |
| 919 | - // Check if strand starts at this obstacle | |
| 920 | - if (strand.startObstacle === this) { | |
| 921 | - // Update the start position to maintain the attachment | |
| 922 | - let angle = strand.startAngle; // Use stored angle | |
| 923 | - strand.start.x = this.x + cos(angle) * this.radius; | |
| 924 | - strand.start.y = this.y + sin(angle) * this.radius; | |
| 925 | - | |
| 926 | - // Update path if it exists | |
| 927 | - if (strand.path && strand.path.length > 0) { | |
| 928 | - strand.path[0].x = strand.start.x; | |
| 929 | - strand.path[0].y = strand.start.y; | |
| 1071 | + updateAttachedStrands () { | |
| 1072 | + // Update web strands that are connected to this obstacle | |
| 1073 | + for (let strand of webStrands) { | |
| 1074 | + if (!strand || strand.broken) continue | |
| 1075 | + | |
| 1076 | + // Check if strand starts at this obstacle | |
| 1077 | + if (strand.startObstacle === this) { | |
| 1078 | + // Update the start position to maintain the attachment | |
| 1079 | + let angle = strand.startAngle // Use stored angle | |
| 1080 | + strand.start.x = this.x + cos(angle) * this.radius | |
| 1081 | + strand.start.y = this.y + sin(angle) * this.radius | |
| 1082 | + | |
| 1083 | + // Update path if it exists | |
| 1084 | + if (strand.path && strand.path.length > 0) { | |
| 1085 | + strand.path[0].x = strand.start.x | |
| 1086 | + strand.path[0].y = strand.start.y | |
| 1087 | + } | |
| 930 | 1088 | } |
| 931 | - } | |
| 932 | - | |
| 933 | - // Check if strand ends at this obstacle | |
| 934 | - if (strand.endObstacle === this) { | |
| 935 | - // Update the end position to maintain the attachment | |
| 936 | - let angle = strand.endAngle; // Use stored angle | |
| 937 | - strand.end.x = this.x + cos(angle) * this.radius; | |
| 938 | - strand.end.y = this.y + sin(angle) * this.radius; | |
| 939 | - | |
| 940 | - // Update path if it exists | |
| 941 | - if (strand.path && strand.path.length > 0) { | |
| 942 | - strand.path[strand.path.length - 1].x = strand.end.x; | |
| 943 | - strand.path[strand.path.length - 1].y = strand.end.y; | |
| 1089 | + | |
| 1090 | + // Check if strand ends at this obstacle | |
| 1091 | + if (strand.endObstacle === this) { | |
| 1092 | + // Update the end position to maintain the attachment | |
| 1093 | + let angle = strand.endAngle // Use stored angle | |
| 1094 | + strand.end.x = this.x + cos(angle) * this.radius | |
| 1095 | + strand.end.y = this.y + sin(angle) * this.radius | |
| 1096 | + | |
| 1097 | + // Update path if it exists | |
| 1098 | + if (strand.path && strand.path.length > 0) { | |
| 1099 | + strand.path[strand.path.length - 1].x = strand.end.x | |
| 1100 | + strand.path[strand.path.length - 1].y = strand.end.y | |
| 1101 | + } | |
| 944 | 1102 | } |
| 945 | 1103 | } |
| 946 | - } | |
| 947 | - | |
| 948 | - // Also update web nodes attached to this obstacle | |
| 949 | - for (let node of webNodes) { | |
| 950 | - if (node.attachedObstacle === this) { | |
| 951 | - let angle = node.attachmentAngle; | |
| 952 | - node.x = this.x + cos(angle) * this.radius; | |
| 953 | - node.y = this.y + sin(angle) * this.radius; | |
| 1104 | + | |
| 1105 | + // Also update web nodes attached to this obstacle | |
| 1106 | + for (let node of webNodes) { | |
| 1107 | + if (node.attachedObstacle === this) { | |
| 1108 | + let angle = node.attachmentAngle | |
| 1109 | + node.x = this.x + cos(angle) * this.radius | |
| 1110 | + node.y = this.y + sin(angle) * this.radius | |
| 1111 | + } | |
| 954 | 1112 | } |
| 955 | 1113 | } |
| 956 | -} | |
| 957 | - | |
| 958 | 1114 | |
| 959 | 1115 | breakAttachedStrands () { |
| 960 | 1116 | // Check for strands attached to this obstacle's edge |
@@ -2072,7 +2228,6 @@ class Bird { | ||
| 2072 | 2228 | |
| 2073 | 2229 | // Warning notifications - but limited to prevent spam |
| 2074 | 2230 | if (notifications.length < 3) { |
| 2075 | - // Limit notifications | |
| 2076 | 2231 | if (jumpStamina <= 20) { |
| 2077 | 2232 | notifications.push( |
| 2078 | 2233 | new Notification('CRITICAL STAMINA!', color(255, 50, 50)) |
@@ -2083,6 +2238,9 @@ class Bird { | ||
| 2083 | 2238 | ) |
| 2084 | 2239 | } |
| 2085 | 2240 | } |
| 2241 | + | |
| 2242 | + // POINTS: Award 2 points for surviving a bird attack | |
| 2243 | + playerPoints += 2 | |
| 2086 | 2244 | } |
| 2087 | 2245 | |
| 2088 | 2246 | // Bird bounces off |
js/game.jsmodified@@ -19,6 +19,7 @@ let gameOverTimer = 0 | ||
| 19 | 19 | let deathReason = '' |
| 20 | 20 | let finalScore = 0 |
| 21 | 21 | let screenShake = 0 |
| 22 | +let fliesSpawnedThisNight = 0 | |
| 22 | 23 | |
| 23 | 24 | // Resources |
| 24 | 25 | let webSilk = 100 |
@@ -68,6 +69,7 @@ let isExhausted = false | ||
| 68 | 69 | let fliesMunchedLastNight = 0 |
| 69 | 70 | let birds = [] |
| 70 | 71 | let staminaRegenCooldown = 0 |
| 72 | +let staminaBonus = 0; | |
| 71 | 73 | |
| 72 | 74 | // PHASE 4B: Wind System |
| 73 | 75 | let windActive = false |
@@ -733,33 +735,40 @@ function draw () { | ||
| 733 | 735 | gamePhase = 'DAWN' |
| 734 | 736 | phaseTimer = 0 |
| 735 | 737 | |
| 736 | - // Calculate dawn stamina with CLEAR BONUS DISPLAY | |
| 737 | - let baseStamina = 30 | |
| 738 | - let flyBonus = fliesMunchedLastNight * 10 | |
| 739 | - staminaBonus = flyBonus | |
| 738 | + // NEW STAMINA CALCULATION: | |
| 739 | + // Fixed 100 max stamina, but starting amount depends on performance | |
| 740 | + maxJumpStamina = 100 // Always 100 max | |
| 740 | 741 | |
| 741 | - maxJumpStamina = baseStamina + flyBonus | |
| 742 | - maxJumpStamina = min(maxJumpStamina, 200) | |
| 743 | - jumpStamina = maxJumpStamina | |
| 742 | + // Calculate percentage of flies munched | |
| 743 | + let totalFliesInNight = fliesSpawnedThisNight + flies.length // Spawned + any remaining | |
| 744 | + let munchPercentage = fliesMunchedLastNight / totalFliesInNight | |
| 744 | 745 | |
| 745 | - // IMPROVED: Combine notifications to reduce overlap | |
| 746 | - let staminaMessage = `Dawn: ${maxJumpStamina} stamina (${baseStamina} + ${flyBonus} from ${fliesMunchedLastNight} flies)` | |
| 747 | - let warningMessage = '' | |
| 746 | + // Base stamina: 20 minimum, up to 100 for 50% or more flies munched | |
| 747 | + if (munchPercentage >= 0.5) { | |
| 748 | + jumpStamina = 100 // Full stamina for eating 50%+ of flies | |
| 749 | + } else { | |
| 750 | + // Scale from 20 to 100 based on 0% to 50% munched | |
| 751 | + jumpStamina = Math.floor(20 + munchPercentage * 2 * 80) | |
| 752 | + } | |
| 753 | + | |
| 754 | + // Create informative notification | |
| 755 | + let percentEaten = Math.floor(munchPercentage * 100) | |
| 756 | + let staminaMessage = `Dawn: ${jumpStamina}/100 stamina (${percentEaten}% of ${totalFliesInNight} flies eaten)` | |
| 748 | 757 | |
| 749 | - if (maxJumpStamina <= 50) { | |
| 750 | - warningMessage = ' ⚠️ DANGER!' | |
| 758 | + if (jumpStamina <= 30) { | |
| 751 | 759 | notifications.push( |
| 752 | - new Notification(staminaMessage + warningMessage, color(255, 50, 50)) | |
| 760 | + new Notification(staminaMessage + ' ⚠️ DANGER!', color(255, 50, 50)) | |
| 753 | 761 | ) |
| 754 | - } else if (maxJumpStamina <= 80) { | |
| 755 | - warningMessage = ' - Low stamina!' | |
| 762 | + } else if (jumpStamina <= 60) { | |
| 756 | 763 | notifications.push( |
| 757 | - new Notification(staminaMessage + warningMessage, color(255, 150, 50)) | |
| 764 | + new Notification( | |
| 765 | + staminaMessage + ' - Low stamina!', | |
| 766 | + color(255, 150, 50) | |
| 767 | + ) | |
| 758 | 768 | ) |
| 759 | - } else if (maxJumpStamina >= 150) { | |
| 760 | - warningMessage = ' - Well fed!' | |
| 769 | + } else if (jumpStamina >= 90) { | |
| 761 | 770 | notifications.push( |
| 762 | - new Notification(staminaMessage + warningMessage, color(100, 255, 100)) | |
| 771 | + new Notification(staminaMessage + ' - Well fed!', color(100, 255, 100)) | |
| 763 | 772 | ) |
| 764 | 773 | } else { |
| 765 | 774 | notifications.push(new Notification(staminaMessage, color(255, 200, 100))) |
@@ -1116,6 +1125,8 @@ function draw () { | ||
| 1116 | 1125 | |
| 1117 | 1126 | jumpStamina += regen |
| 1118 | 1127 | jumpStamina = min(jumpStamina, maxJumpStamina) |
| 1128 | + | |
| 1129 | + // FIX: Only set exhausted when truly out of stamina | |
| 1119 | 1130 | isExhausted = jumpStamina < jumpCost |
| 1120 | 1131 | |
| 1121 | 1132 | // Update and display birds |
@@ -1234,6 +1245,7 @@ function draw () { | ||
| 1234 | 1245 | if (flyType === 'queen') fly.baseSpeed *= 0.5 |
| 1235 | 1246 | fly.currentSpeed = fly.baseSpeed |
| 1236 | 1247 | flies.push(fly) |
| 1248 | + fliesSpawnedThisNight++ // Track dynamic spawn | |
| 1237 | 1249 | } |
| 1238 | 1250 | if (phaseTimer % 300 === 0 && foodBoxes.length < 6) { |
| 1239 | 1251 | spawnFoodBox() |
@@ -1927,6 +1939,9 @@ function applyUpgradeEffects () { | ||
| 1927 | 1939 | } |
| 1928 | 1940 | |
| 1929 | 1941 | function spawnNightFlies () { |
| 1942 | + // Reset counter for new night | |
| 1943 | + fliesSpawnedThisNight = 0 | |
| 1944 | + | |
| 1930 | 1945 | // Base flies + more per night |
| 1931 | 1946 | let numFlies = 5 + currentNight |
| 1932 | 1947 | |
@@ -1956,6 +1971,7 @@ function spawnNightFlies () { | ||
| 1956 | 1971 | if (flyType === 'queen') fly.baseSpeed *= 0.5 // Queens are much slower |
| 1957 | 1972 | fly.currentSpeed = fly.baseSpeed |
| 1958 | 1973 | flies.push(fly) |
| 1974 | + fliesSpawnedThisNight++ // Track spawn | |
| 1959 | 1975 | } |
| 1960 | 1976 | |
| 1961 | 1977 | // PHASE 2: Guarantee at least 1 golden fly per night |
@@ -1964,6 +1980,7 @@ function spawnNightFlies () { | ||
| 1964 | 1980 | goldenFly.baseSpeed = baseFlySpeed * flySpeedMultiplier * 1.3 |
| 1965 | 1981 | goldenFly.currentSpeed = goldenFly.baseSpeed |
| 1966 | 1982 | flies.push(goldenFly) |
| 1983 | + fliesSpawnedThisNight++ // Track spawn | |
| 1967 | 1984 | // Add notification |
| 1968 | 1985 | notifications.push( |
| 1969 | 1986 | new Notification('Golden Firefly Appeared! ✨', color(255, 215, 0)) |
@@ -1979,6 +1996,7 @@ function spawnNightFlies () { | ||
| 1979 | 1996 | queenFly.baseSpeed = baseFlySpeed * flySpeedMultiplier * 0.5 |
| 1980 | 1997 | queenFly.currentSpeed = queenFly.baseSpeed |
| 1981 | 1998 | flies.push(queenFly) |
| 1999 | + fliesSpawnedThisNight++ // Track spawn | |
| 1982 | 2000 | // Add notification |
| 1983 | 2001 | notifications.push( |
| 1984 | 2002 | new Notification('Queen Firefly Arrived! 👑', color(200, 100, 255)) |
@@ -2509,50 +2527,57 @@ function updateResources () { | ||
| 2509 | 2527 | } |
| 2510 | 2528 | } |
| 2511 | 2529 | |
| 2512 | -function handleWebDeployment() { | |
| 2530 | +function handleWebDeployment () { | |
| 2513 | 2531 | // Handle keyboard-based web deployment |
| 2514 | 2532 | if (spacePressed && spider.isAirborne && !isDeployingWeb && webSilk > 10) { |
| 2515 | - isDeployingWeb = true; | |
| 2516 | - currentStrand = new WebStrand(spider.lastAnchorPoint.copy(), null); | |
| 2517 | - currentStrand.path = [spider.lastAnchorPoint.copy()]; | |
| 2518 | - | |
| 2533 | + isDeployingWeb = true | |
| 2534 | + currentStrand = new WebStrand(spider.lastAnchorPoint.copy(), null) | |
| 2535 | + currentStrand.path = [spider.lastAnchorPoint.copy()] | |
| 2536 | + | |
| 2519 | 2537 | // NEW: Check if starting from an obstacle |
| 2520 | 2538 | for (let obstacle of obstacles) { |
| 2521 | - let d = dist(spider.lastAnchorPoint.x, spider.lastAnchorPoint.y, obstacle.x, obstacle.y); | |
| 2539 | + let d = dist( | |
| 2540 | + spider.lastAnchorPoint.x, | |
| 2541 | + spider.lastAnchorPoint.y, | |
| 2542 | + obstacle.x, | |
| 2543 | + obstacle.y | |
| 2544 | + ) | |
| 2522 | 2545 | // Check if anchor is on obstacle edge (within tolerance) |
| 2523 | 2546 | if (abs(d - obstacle.radius) < 10) { |
| 2524 | - currentStrand.startObstacle = obstacle; | |
| 2547 | + currentStrand.startObstacle = obstacle | |
| 2525 | 2548 | currentStrand.startAngle = atan2( |
| 2526 | 2549 | spider.lastAnchorPoint.y - obstacle.y, |
| 2527 | 2550 | spider.lastAnchorPoint.x - obstacle.x |
| 2528 | - ); | |
| 2529 | - break; | |
| 2551 | + ) | |
| 2552 | + break | |
| 2530 | 2553 | } |
| 2531 | 2554 | } |
| 2532 | - | |
| 2533 | - webStrands.push(currentStrand); | |
| 2534 | - | |
| 2535 | - let newNode = new WebNode(spider.lastAnchorPoint.x, spider.lastAnchorPoint.y); | |
| 2555 | + | |
| 2556 | + webStrands.push(currentStrand) | |
| 2557 | + | |
| 2558 | + let newNode = new WebNode( | |
| 2559 | + spider.lastAnchorPoint.x, | |
| 2560 | + spider.lastAnchorPoint.y | |
| 2561 | + ) | |
| 2536 | 2562 | // NEW: Track node attachment if on obstacle |
| 2537 | 2563 | if (currentStrand.startObstacle) { |
| 2538 | - newNode.attachedObstacle = currentStrand.startObstacle; | |
| 2539 | - newNode.attachmentAngle = currentStrand.startAngle; | |
| 2564 | + newNode.attachedObstacle = currentStrand.startObstacle | |
| 2565 | + newNode.attachmentAngle = currentStrand.startAngle | |
| 2540 | 2566 | } |
| 2541 | - webNodes.push(newNode); | |
| 2567 | + webNodes.push(newNode) | |
| 2542 | 2568 | } |
| 2543 | 2569 | |
| 2544 | 2570 | // Update web for keyboard controls |
| 2545 | 2571 | if (currentStrand && isDeployingWeb && spider.isAirborne && spacePressed) { |
| 2546 | - currentStrand.end = spider.pos.copy(); | |
| 2572 | + currentStrand.end = spider.pos.copy() | |
| 2547 | 2573 | if (frameCount % 2 === 0) { |
| 2548 | - currentStrand.path.push(spider.pos.copy()); | |
| 2574 | + currentStrand.path.push(spider.pos.copy()) | |
| 2549 | 2575 | } |
| 2550 | 2576 | } |
| 2551 | 2577 | |
| 2552 | 2578 | // Touch-based web deployment is handled in touchMoved() |
| 2553 | 2579 | } |
| 2554 | 2580 | |
| 2555 | - | |
| 2556 | 2581 | function updateUI () { |
| 2557 | 2582 | // Update control instructions based on device |
| 2558 | 2583 | let isMobile = |
@@ -2583,14 +2608,15 @@ function updateUI () { | ||
| 2583 | 2608 | '<br>' + |
| 2584 | 2609 | 'Web Strands: <span id="strand-count">0</span><br>' + |
| 2585 | 2610 | 'Flies Caught: <span id="flies-caught">0</span> | Munched: <span id="flies-munched">0</span><br>' + |
| 2586 | - 'Total Score: <span id="total-score">0</span>' | |
| 2611 | + 'Points: <span id="player-points">0</span> | Total Score: <span id="total-score">0</span>' | |
| 2587 | 2612 | |
| 2588 | - // PHASE 1 UPDATES | |
| 2613 | + // Update all the displays | |
| 2589 | 2614 | document.getElementById('strand-count').textContent = webStrands.filter( |
| 2590 | 2615 | s => !s.broken |
| 2591 | 2616 | ).length |
| 2592 | 2617 | document.getElementById('flies-caught').textContent = fliesCaught |
| 2593 | 2618 | document.getElementById('flies-munched').textContent = fliesMunched |
| 2619 | + document.getElementById('player-points').textContent = playerPoints // NEW | |
| 2594 | 2620 | document.getElementById('total-score').textContent = totalFliesCaught |
| 2595 | 2621 | |
| 2596 | 2622 | // Update phase display |
@@ -2622,27 +2648,40 @@ function updateUI () { | ||
| 2622 | 2648 | timerText + |
| 2623 | 2649 | `<br><small ${staminaColor}>Dawn Stamina: ${potentialStamina}</small>` |
| 2624 | 2650 | |
| 2625 | - if (gamePhase === 'DUSK') { | |
| 2626 | - let timeLeft = Math.ceil((DUSK_DURATION - phaseTimer) / 60) | |
| 2627 | - timerText = `${timeLeft}s to prepare!` | |
| 2628 | - } else if (gamePhase === 'NIGHT') { | |
| 2629 | - let timeLeft = Math.ceil((NIGHT_DURATION - phaseTimer) / 60) | |
| 2630 | - // PHASE 2: Count different fly types | |
| 2631 | - let regularCount = flies.filter(f => f.type === 'regular').length | |
| 2632 | - let goldenCount = flies.filter(f => f.type === 'golden').length | |
| 2633 | - let mothCount = flies.filter(f => f.type === 'moth').length | |
| 2634 | - let queenCount = flies.filter(f => f.type === 'queen').length | |
| 2635 | - | |
| 2636 | - timerText = `${timeLeft}s • ${flies.length} flies` | |
| 2637 | - | |
| 2638 | - // Show special fly counts if any | |
| 2639 | - if (goldenCount > 0 || mothCount > 0 || queenCount > 0) { | |
| 2640 | - let specialCounts = [] | |
| 2641 | - if (queenCount > 0) specialCounts.push(`${queenCount}👑`) | |
| 2642 | - if (goldenCount > 0) specialCounts.push(`${goldenCount}✨`) | |
| 2643 | - if (mothCount > 0) specialCounts.push(`${mothCount}🦋`) | |
| 2644 | - timerText += ` (${specialCounts.join(' ')})` | |
| 2645 | - } | |
| 2651 | + if (gamePhase === 'NIGHT') { | |
| 2652 | + let timeLeft = Math.ceil((NIGHT_DURATION - phaseTimer) / 60); | |
| 2653 | + | |
| 2654 | + // Calculate current munch percentage | |
| 2655 | + let totalFliesInNight = fliesSpawnedThisNight + flies.length; | |
| 2656 | + let currentMunchPercent = totalFliesInNight > 0 ? | |
| 2657 | + Math.floor((fliesMunched / totalFliesInNight) * 100) : 0; | |
| 2658 | + | |
| 2659 | + // Calculate predicted dawn stamina | |
| 2660 | + let predictedStamina; | |
| 2661 | + if (currentMunchPercent >= 50) { | |
| 2662 | + predictedStamina = 100; | |
| 2663 | + } else { | |
| 2664 | + predictedStamina = Math.floor(20 + (currentMunchPercent * 2) * 0.8); | |
| 2665 | + } | |
| 2666 | + | |
| 2667 | + timerText = `${timeLeft}s • ${flies.length} flies`; | |
| 2668 | + | |
| 2669 | + // Show special fly counts if any | |
| 2670 | + let goldenCount = flies.filter(f => f.type === 'golden').length; | |
| 2671 | + let mothCount = flies.filter(f => f.type === 'moth').length; | |
| 2672 | + let queenCount = flies.filter(f => f.type === 'queen').length; | |
| 2673 | + | |
| 2674 | + if (goldenCount > 0 || mothCount > 0 || queenCount > 0) { | |
| 2675 | + let specialCounts = []; | |
| 2676 | + if (queenCount > 0) specialCounts.push(`${queenCount}👑`); | |
| 2677 | + if (goldenCount > 0) specialCounts.push(`${goldenCount}✨`); | |
| 2678 | + if (mothCount > 0) specialCounts.push(`${mothCount}🦋`); | |
| 2679 | + timerText += ` (${specialCounts.join(' ')})`; | |
| 2680 | + } | |
| 2681 | + // Show munch progress | |
| 2682 | + document.getElementById('timer').innerHTML = timerText + | |
| 2683 | + `<br><small style="color: ${predictedStamina < 40 ? '#ff4444' : predictedStamina < 70 ? '#ffaa44' : '#44ff44'}">` + | |
| 2684 | + `Munched: ${currentMunchPercent}% → ${predictedStamina} dawn stamina</small>`; | |
| 2646 | 2685 | } else if (gamePhase === 'DAWN') { |
| 2647 | 2686 | let timeLeft = Math.ceil((DAWN_DURATION - phaseTimer) / 60) |
| 2648 | 2687 | // PHASE 4: Show birds and exhaustion status |
@@ -2687,48 +2726,49 @@ function updateUI () { | ||
| 2687 | 2726 | } |
| 2688 | 2727 | |
| 2689 | 2728 | // PHASE 4: Update meter based on phase |
| 2690 | - if (gamePhase === 'DAWN') { | |
| 2691 | - // Show stamina instead of silk during dawn | |
| 2692 | - document.getElementById('web-meter-label').textContent = 'STAMINA' | |
| 2693 | - let staminaPercent = (jumpStamina / maxJumpStamina) * 100 | |
| 2694 | - document.getElementById('web-meter-fill').style.width = staminaPercent + '%' | |
| 2695 | - | |
| 2696 | - // Color based on stamina level | |
| 2697 | - if (jumpStamina < jumpCost) { | |
| 2698 | - // Exhausted - red flash | |
| 2699 | - let flash = sin(frameCount * 0.3) * 0.5 + 0.5 | |
| 2700 | - document.getElementById( | |
| 2701 | - 'web-meter-fill' | |
| 2702 | - ).style.background = `linear-gradient(90deg, rgb(255, ${ | |
| 2703 | - 50 + flash * 50 | |
| 2704 | - }, ${50 + flash * 50}), rgb(200, ${30 + flash * 30}, ${30 + flash * 30}))` | |
| 2705 | - } else if (jumpStamina < maxJumpStamina * 0.3) { | |
| 2706 | - // Very tired - orange-red | |
| 2707 | - document.getElementById('web-meter-fill').style.background = | |
| 2708 | - 'linear-gradient(90deg, #FF6B35, #FF4444)' | |
| 2709 | - } else if (jumpStamina < maxJumpStamina * 0.5) { | |
| 2710 | - // Tired - orange | |
| 2711 | - document.getElementById('web-meter-fill').style.background = | |
| 2712 | - 'linear-gradient(90deg, #FFA500, #FF8C00)' | |
| 2713 | - } else { | |
| 2714 | - // Good stamina - yellow-orange | |
| 2715 | - document.getElementById('web-meter-fill').style.background = | |
| 2716 | - 'linear-gradient(90deg, #FFD700, #FFA500)' | |
| 2717 | - } | |
| 2718 | - if (jumpStamina <= 0 && !gameOver) { | |
| 2719 | - push() | |
| 2720 | - fill(255, 0, 0, 50 + sin(frameCount * 0.3) * 50) | |
| 2721 | - rect(0, 0, width, height) | |
| 2722 | - | |
| 2723 | - textAlign(CENTER) | |
| 2724 | - textSize(32) | |
| 2725 | - fill(255, 50, 50) | |
| 2726 | - stroke(0) | |
| 2727 | - strokeWeight(3) | |
| 2728 | - text('NO STAMINA - AVOID BIRDS!', width / 2, height / 2) | |
| 2729 | - pop() | |
| 2730 | - } | |
| 2729 | +if (gamePhase === 'DAWN') { | |
| 2730 | + // Show stamina instead of silk during dawn | |
| 2731 | + document.getElementById('web-meter-label').textContent = 'STAMINA'; | |
| 2732 | + | |
| 2733 | + // FIX: Always show percentage out of 100, not out of variable max | |
| 2734 | + let staminaPercent = (jumpStamina / 100) * 100; // Always out of 100 | |
| 2735 | + document.getElementById('web-meter-fill').style.width = staminaPercent + '%'; | |
| 2736 | + | |
| 2737 | + // Color based on stamina level | |
| 2738 | + if (jumpStamina < 20) { | |
| 2739 | + // Exhausted - red flash | |
| 2740 | + let flash = sin(frameCount * 0.3) * 0.5 + 0.5; | |
| 2741 | + document.getElementById('web-meter-fill').style.background = | |
| 2742 | + `linear-gradient(90deg, rgb(255, ${50 + flash * 50}, ${50 + flash * 50}), rgb(200, ${30 + flash * 30}, ${30 + flash * 30}))`; | |
| 2743 | + } else if (jumpStamina < 40) { | |
| 2744 | + // Very tired - orange-red | |
| 2745 | + document.getElementById('web-meter-fill').style.background = 'linear-gradient(90deg, #FF6B35, #FF4444)'; | |
| 2746 | + } else if (jumpStamina < 60) { | |
| 2747 | + // Tired - orange | |
| 2748 | + document.getElementById('web-meter-fill').style.background = 'linear-gradient(90deg, #FFA500, #FF8C00)'; | |
| 2749 | + } else if (jumpStamina < 80) { | |
| 2750 | + // OK - yellow-orange | |
| 2751 | + document.getElementById('web-meter-fill').style.background = 'linear-gradient(90deg, #FFD700, #FFA500)'; | |
| 2731 | 2752 | } else { |
| 2753 | + // Good stamina - green-yellow | |
| 2754 | + document.getElementById('web-meter-fill').style.background = 'linear-gradient(90deg, #90EE90, #FFD700)'; | |
| 2755 | + } | |
| 2756 | + | |
| 2757 | + // Show critical warning overlay | |
| 2758 | + if (jumpStamina <= 0 && !gameOver) { | |
| 2759 | + push(); | |
| 2760 | + fill(255, 0, 0, 50 + sin(frameCount * 0.3) * 50); | |
| 2761 | + rect(0, 0, width, height); | |
| 2762 | + | |
| 2763 | + textAlign(CENTER); | |
| 2764 | + textSize(32); | |
| 2765 | + fill(255, 50, 50); | |
| 2766 | + stroke(0); | |
| 2767 | + strokeWeight(3); | |
| 2768 | + text('NO STAMINA - AVOID BIRDS!', width / 2, height / 2); | |
| 2769 | + pop(); | |
| 2770 | + } | |
| 2771 | +} else { | |
| 2732 | 2772 | // Normal silk meter |
| 2733 | 2773 | document.getElementById('web-meter-label').textContent = 'SILK' |
| 2734 | 2774 | let meterPercent = (webSilk / maxWebSilk) * 100 |
@@ -2840,6 +2880,7 @@ window.restartGame = function () { | ||
| 2840 | 2880 | currentNight = 1 |
| 2841 | 2881 | fliesCaught = 0 |
| 2842 | 2882 | fliesMunched = 0 |
| 2883 | + playerPoints = 0 | |
| 2843 | 2884 | totalFliesCaught = 0 |
| 2844 | 2885 | jumpStamina = 100 |
| 2845 | 2886 | maxJumpStamina = 100 |
@@ -3023,58 +3064,67 @@ function recycleNearbyWeb () { | ||
| 3023 | 3064 | } |
| 3024 | 3065 | } |
| 3025 | 3066 | |
| 3026 | -function touchStarted() { | |
| 3067 | +function touchStarted () { | |
| 3027 | 3068 | if (touches.length > 0) { |
| 3028 | - touchStartTime = millis(); | |
| 3029 | - touchStartX = touches[0].x; | |
| 3030 | - touchStartY = touches[0].y; | |
| 3069 | + touchStartTime = millis() | |
| 3070 | + touchStartX = touches[0].x | |
| 3071 | + touchStartY = touches[0].y | |
| 3031 | 3072 | |
| 3032 | 3073 | // Check for double tap on spider to munch |
| 3033 | - let touchOnSpider = dist(touches[0].x, touches[0].y, spider.pos.x, spider.pos.y) < 30; | |
| 3074 | + let touchOnSpider = | |
| 3075 | + dist(touches[0].x, touches[0].y, spider.pos.x, spider.pos.y) < 30 | |
| 3034 | 3076 | |
| 3035 | 3077 | if (touchOnSpider && millis() - lastTapTime < 300) { |
| 3036 | 3078 | // Double tap detected on spider - MUNCH! |
| 3037 | - spider.munch(); | |
| 3038 | - lastTapTime = 0; // Reset to prevent triple tap | |
| 3079 | + spider.munch() | |
| 3080 | + lastTapTime = 0 // Reset to prevent triple tap | |
| 3039 | 3081 | } else if (!spider.isAirborne) { |
| 3040 | 3082 | // Single tap while on ground - jump |
| 3041 | - spider.jump(touches[0].x, touches[0].y); | |
| 3042 | - lastTapTime = millis(); | |
| 3083 | + spider.jump(touches[0].x, touches[0].y) | |
| 3084 | + lastTapTime = millis() | |
| 3043 | 3085 | } else if (spider.isAirborne && webSilk > 10 && !isDeployingWeb) { |
| 3044 | 3086 | // Start web deployment if airborne (only if not already deploying) |
| 3045 | - touchHolding = true; | |
| 3046 | - isDeployingWeb = true; | |
| 3047 | - currentStrand = new WebStrand(spider.lastAnchorPoint.copy(), null); | |
| 3048 | - currentStrand.path = [spider.lastAnchorPoint.copy()]; | |
| 3049 | - | |
| 3087 | + touchHolding = true | |
| 3088 | + isDeployingWeb = true | |
| 3089 | + currentStrand = new WebStrand(spider.lastAnchorPoint.copy(), null) | |
| 3090 | + currentStrand.path = [spider.lastAnchorPoint.copy()] | |
| 3091 | + | |
| 3050 | 3092 | for (let obstacle of obstacles) { |
| 3051 | - let d = dist(spider.lastAnchorPoint.x, spider.lastAnchorPoint.y, obstacle.x, obstacle.y); | |
| 3093 | + let d = dist( | |
| 3094 | + spider.lastAnchorPoint.x, | |
| 3095 | + spider.lastAnchorPoint.y, | |
| 3096 | + obstacle.x, | |
| 3097 | + obstacle.y | |
| 3098 | + ) | |
| 3052 | 3099 | // Check if anchor is on obstacle edge (within tolerance) |
| 3053 | 3100 | if (abs(d - obstacle.radius) < 10) { |
| 3054 | - currentStrand.startObstacle = obstacle; | |
| 3101 | + currentStrand.startObstacle = obstacle | |
| 3055 | 3102 | currentStrand.startAngle = atan2( |
| 3056 | 3103 | spider.lastAnchorPoint.y - obstacle.y, |
| 3057 | 3104 | spider.lastAnchorPoint.x - obstacle.x |
| 3058 | - ); | |
| 3059 | - break; | |
| 3105 | + ) | |
| 3106 | + break | |
| 3060 | 3107 | } |
| 3061 | 3108 | } |
| 3062 | - | |
| 3063 | - webStrands.push(currentStrand); | |
| 3064 | - | |
| 3065 | - let newNode = new WebNode(spider.lastAnchorPoint.x, spider.lastAnchorPoint.y); | |
| 3109 | + | |
| 3110 | + webStrands.push(currentStrand) | |
| 3111 | + | |
| 3112 | + let newNode = new WebNode( | |
| 3113 | + spider.lastAnchorPoint.x, | |
| 3114 | + spider.lastAnchorPoint.y | |
| 3115 | + ) | |
| 3066 | 3116 | // NEW: Track node attachment if on obstacle |
| 3067 | 3117 | if (currentStrand.startObstacle) { |
| 3068 | - newNode.attachedObstacle = currentStrand.startObstacle; | |
| 3069 | - newNode.attachmentAngle = currentStrand.startAngle; | |
| 3118 | + newNode.attachedObstacle = currentStrand.startObstacle | |
| 3119 | + newNode.attachmentAngle = currentStrand.startAngle | |
| 3070 | 3120 | } |
| 3071 | - webNodes.push(newNode); | |
| 3121 | + webNodes.push(newNode) | |
| 3072 | 3122 | } else if (spider.isAirborne && isDeployingWeb) { |
| 3073 | 3123 | // If already deploying and user taps again, just continue (don't create new strand) |
| 3074 | - touchHolding = true; | |
| 3124 | + touchHolding = true | |
| 3075 | 3125 | } |
| 3076 | 3126 | } |
| 3077 | - return false; // Prevent default | |
| 3127 | + return false // Prevent default | |
| 3078 | 3128 | } |
| 3079 | 3129 | |
| 3080 | 3130 | function touchMoved () { |