expand on phases, game loop
- SHA
79340ef8e8cf418a0fe18106ee23f7c31278076e- Parents
-
503d6d2 - Tree
a0b5340
79340ef
79340ef8e8cf418a0fe18106ee23f7c31278076e503d6d2
a0b5340| Status | File | + | - |
|---|---|---|---|
| M |
index.html
|
59 | 2 |
| M |
js/entities.js
|
603 | 604 |
| M |
js/game.js
|
1457 | 53 |
index.htmlmodified@@ -12,17 +12,74 @@ | ||
| 12 | 12 | <div id="info"> |
| 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 | - Flies Caught: <span id="flies-caught">0</span> | Munched: <span id="flies-munched">0</span> | |
| 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 | 17 | </div> |
| 17 | 18 | <div id="phase-indicator"> |
| 18 | 19 | <span id="phase">DUSK</span><br> |
| 19 | - <span id="timer"></span> | |
| 20 | + <span id="timer"></span><br> | |
| 21 | + <span id="night-counter" style="font-size: 14px; color: #FFD700;">Night 1</span> | |
| 20 | 22 | </div> |
| 21 | 23 | <div id="web-meter-label">SILK</div> |
| 22 | 24 | <div id="web-meter"> |
| 23 | 25 | <div id="web-meter-fill"></div> |
| 24 | 26 | </div> |
| 25 | 27 | </div> |
| 28 | + | |
| 29 | + <!-- PHASE 5: Stats & Skins Panel --> | |
| 30 | + <div id="stats-panel" style="display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); | |
| 31 | + background: rgba(20, 20, 40, 0.95); border: 3px solid #87CEEB; border-radius: 20px; | |
| 32 | + padding: 30px; color: white; z-index: 100; min-width: 500px; max-width: 700px; max-height: 80vh; overflow-y: auto;"> | |
| 33 | + <h2 style="text-align: center; color: #87CEEB; margin-top: 0;">📊 Statistics & Skins 📊</h2> | |
| 34 | + | |
| 35 | + <div id="stats-content" style="margin-bottom: 20px;"> | |
| 36 | + <h3 style="color: #FFD700;">Lifetime Stats</h3> | |
| 37 | + <div id="stats-list" style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;"></div> | |
| 38 | + </div> | |
| 39 | + | |
| 40 | + <div id="skins-content" style="margin-bottom: 20px;"> | |
| 41 | + <h3 style="color: #FFD700;">Spider Skins</h3> | |
| 42 | + <div id="skins-list" style="display: flex; gap: 15px; flex-wrap: wrap; justify-content: center;"></div> | |
| 43 | + </div> | |
| 44 | + | |
| 45 | + <div id="achievements-content"> | |
| 46 | + <h3 style="color: #FFD700;">Achievements</h3> | |
| 47 | + <div id="achievements-list" style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; max-height: 200px; overflow-y: auto;"></div> | |
| 48 | + </div> | |
| 49 | + | |
| 50 | + <button id="close-stats-btn" style="width: 100%; padding: 15px; font-size: 18px; margin-top: 20px; | |
| 51 | + background: #87CEEB; color: #000; border: none; | |
| 52 | + border-radius: 10px; cursor: pointer; font-weight: bold;"> | |
| 53 | + Close | |
| 54 | + </button> | |
| 55 | + </div> | |
| 56 | + | |
| 57 | + <!-- PHASE 3: Upgrade Shop --> | |
| 58 | + <div id="upgrade-shop" style="display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); | |
| 59 | + background: rgba(20, 20, 40, 0.95); border: 3px solid #FFD700; border-radius: 20px; | |
| 60 | + padding: 30px; color: white; z-index: 100; min-width: 400px; max-width: 600px;"> | |
| 61 | + <h2 style="text-align: center; color: #FFD700; margin-top: 0;">🕷️ Spider Upgrades 🕷️</h2> | |
| 62 | + <div id="shop-points" style="text-align: center; font-size: 20px; margin-bottom: 20px;"> | |
| 63 | + Points Available: <span id="available-points">0</span> | |
| 64 | + </div> | |
| 65 | + | |
| 66 | + <div id="tier1-upgrades" style="margin-bottom: 20px;"> | |
| 67 | + <h3 style="color: #87CEEB;">Tier 1 Upgrades</h3> | |
| 68 | + <div id="upgrade-list-tier1"></div> | |
| 69 | + </div> | |
| 70 | + | |
| 71 | + <div id="tier2-upgrades" style="margin-bottom: 20px; opacity: 0.5;"> | |
| 72 | + <h3 style="color: #FFB6C1;">Tier 2 Upgrades (Unlock Tier 1 First)</h3> | |
| 73 | + <div id="upgrade-list-tier2"></div> | |
| 74 | + </div> | |
| 75 | + | |
| 76 | + <button id="continue-btn" style="width: 100%; padding: 15px; font-size: 18px; | |
| 77 | + background: #FFD700; color: #000; border: none; | |
| 78 | + border-radius: 10px; cursor: pointer; font-weight: bold;"> | |
| 79 | + Continue to Next Night | |
| 80 | + </button> | |
| 81 | + </div> | |
| 82 | + | |
| 26 | 83 | <script src="js/physics.js"></script> |
| 27 | 84 | <script src="js/entities.js"></script> |
| 28 | 85 | <script src="js/game.js"></script> |
js/entities.jsmodified@@ -1,20 +1,20 @@ | ||
| 1 | 1 | // entities.js - All game entity classes |
| 2 | 2 | |
| 3 | 3 | class Spider { |
| 4 | - constructor (x, y) { | |
| 5 | - this.pos = createVector(x, y) | |
| 6 | - this.vel = createVector(0, 0) | |
| 7 | - this.acc = createVector(0, 0) | |
| 8 | - this.radius = 8 | |
| 9 | - this.isAirborne = false | |
| 10 | - this.canJump = true | |
| 11 | - this.lastAnchorPoint = null | |
| 12 | - this.gravity = createVector(0, 0.3) | |
| 13 | - this.jumpPower = 12 | |
| 14 | - this.maxSpeed = 15 | |
| 15 | - this.munchRadius = 20 | |
| 16 | - this.munchCooldown = 0 | |
| 17 | - this.attachedObstacle = null // Track which obstacle spider is on | |
| 4 | + constructor(x, y) { | |
| 5 | + this.pos = createVector(x, y); | |
| 6 | + this.vel = createVector(0, 0); | |
| 7 | + this.acc = createVector(0, 0); | |
| 8 | + this.radius = 8; | |
| 9 | + this.isAirborne = false; | |
| 10 | + this.canJump = true; | |
| 11 | + this.lastAnchorPoint = null; | |
| 12 | + this.gravity = createVector(0, 0.3); | |
| 13 | + this.jumpPower = 12; | |
| 14 | + this.maxSpeed = 15; | |
| 15 | + this.munchRadius = 20; | |
| 16 | + this.munchCooldown = 0; | |
| 17 | + this.attachedObstacle = null; // Track which obstacle spider is on | |
| 18 | 18 | } |
| 19 | 19 | |
| 20 | 20 | jump(targetX, targetY) { |
@@ -80,92 +80,92 @@ class Spider { | ||
| 80 | 80 | } |
| 81 | 81 | } |
| 82 | 82 | |
| 83 | - munch () { | |
| 84 | - if (this.munchCooldown > 0) return | |
| 83 | + munch() { | |
| 84 | + if (this.munchCooldown > 0) return; | |
| 85 | 85 | |
| 86 | - isMunching = true | |
| 87 | - this.munchCooldown = 30 | |
| 86 | + isMunching = true; | |
| 87 | + this.munchCooldown = 30; | |
| 88 | 88 | |
| 89 | 89 | for (let i = flies.length - 1; i >= 0; i--) { |
| 90 | - let fly = flies[i] | |
| 91 | - let d = dist(this.pos.x, this.pos.y, fly.pos.x, fly.pos.y) | |
| 90 | + let fly = flies[i]; | |
| 91 | + let d = dist(this.pos.x, this.pos.y, fly.pos.x, fly.pos.y); | |
| 92 | 92 | if (d < this.munchRadius) { |
| 93 | - fliesMunched++ | |
| 94 | - webSilk = min(webSilk + 15, maxWebSilk) | |
| 93 | + fliesMunched++; | |
| 94 | + webSilk = min(webSilk + 15, maxWebSilk); | |
| 95 | 95 | |
| 96 | 96 | for (let j = 0; j < 12; j++) { |
| 97 | - let p = new Particle(fly.pos.x, fly.pos.y) | |
| 98 | - p.color = color(255, random(100, 255), 0) | |
| 99 | - particles.push(p) | |
| 97 | + let p = new Particle(fly.pos.x, fly.pos.y); | |
| 98 | + p.color = color(255, random(100, 255), 0); | |
| 99 | + particles.push(p); | |
| 100 | 100 | } |
| 101 | 101 | |
| 102 | - flies.splice(i, 1) | |
| 103 | - break | |
| 102 | + flies.splice(i, 1); | |
| 103 | + break; | |
| 104 | 104 | } |
| 105 | 105 | } |
| 106 | 106 | } |
| 107 | 107 | |
| 108 | - update () { | |
| 108 | + update() { | |
| 109 | 109 | // If attached to a moving obstacle, move with it |
| 110 | 110 | if (this.attachedObstacle && !this.isAirborne) { |
| 111 | 111 | // Calculate angle from obstacle center to spider |
| 112 | - let angle = atan2(this.pos.y - this.attachedObstacle.y, this.pos.x - this.attachedObstacle.x) | |
| 112 | + let angle = atan2(this.pos.y - this.attachedObstacle.y, this.pos.x - this.attachedObstacle.x); | |
| 113 | 113 | // Keep spider on the surface of the obstacle |
| 114 | - this.pos.x = this.attachedObstacle.x + cos(angle) * (this.attachedObstacle.radius + this.radius) | |
| 115 | - this.pos.y = this.attachedObstacle.y + sin(angle) * (this.attachedObstacle.radius + this.radius) | |
| 114 | + this.pos.x = this.attachedObstacle.x + cos(angle) * (this.attachedObstacle.radius + this.radius); | |
| 115 | + this.pos.y = this.attachedObstacle.y + sin(angle) * (this.attachedObstacle.radius + this.radius); | |
| 116 | 116 | } |
| 117 | 117 | |
| 118 | 118 | if (this.isAirborne) { |
| 119 | - this.acc.add(this.gravity) | |
| 120 | - this.attachedObstacle = null // Clear attachment when jumping | |
| 119 | + this.acc.add(this.gravity); | |
| 120 | + this.attachedObstacle = null; // Clear attachment when jumping | |
| 121 | 121 | } |
| 122 | 122 | |
| 123 | - this.vel.add(this.acc) | |
| 124 | - this.vel.limit(this.maxSpeed) | |
| 125 | - this.pos.add(this.vel) | |
| 126 | - this.acc.mult(0) | |
| 123 | + this.vel.add(this.acc); | |
| 124 | + this.vel.limit(this.maxSpeed); | |
| 125 | + this.pos.add(this.vel); | |
| 126 | + this.acc.mult(0); | |
| 127 | 127 | |
| 128 | 128 | if (this.munchCooldown > 0) { |
| 129 | - this.munchCooldown-- | |
| 129 | + this.munchCooldown--; | |
| 130 | 130 | if (this.munchCooldown === 0) { |
| 131 | - isMunching = false | |
| 131 | + isMunching = false; | |
| 132 | 132 | } |
| 133 | 133 | } |
| 134 | 134 | |
| 135 | 135 | // Check ground collision |
| 136 | 136 | if (this.pos.y >= height - this.radius) { |
| 137 | - this.pos.y = height - this.radius | |
| 138 | - this.land() | |
| 139 | - this.attachedObstacle = null | |
| 137 | + this.pos.y = height - this.radius; | |
| 138 | + this.land(); | |
| 139 | + this.attachedObstacle = null; | |
| 140 | 140 | } |
| 141 | 141 | |
| 142 | 142 | // Check wall collisions |
| 143 | 143 | if (this.pos.x <= this.radius || this.pos.x >= width - this.radius) { |
| 144 | - this.pos.x = constrain(this.pos.x, this.radius, width - this.radius) | |
| 145 | - this.vel.x *= -0.5 | |
| 144 | + this.pos.x = constrain(this.pos.x, this.radius, width - this.radius); | |
| 145 | + this.vel.x *= -0.5; | |
| 146 | 146 | } |
| 147 | 147 | |
| 148 | 148 | // Check ceiling |
| 149 | 149 | if (this.pos.y <= this.radius) { |
| 150 | - this.pos.y = this.radius | |
| 151 | - this.vel.y *= -0.5 // Bounce off ceiling, don't land | |
| 150 | + this.pos.y = this.radius; | |
| 151 | + this.vel.y *= -0.5; // Bounce off ceiling, don't land | |
| 152 | 152 | } |
| 153 | 153 | |
| 154 | 154 | // Check home branch collision (one-way platform) |
| 155 | 155 | if (window.homeBranch && this.isAirborne && this.vel.y > 0.1) { |
| 156 | 156 | // Only when actually falling |
| 157 | - let branch = window.homeBranch | |
| 157 | + let branch = window.homeBranch; | |
| 158 | 158 | |
| 159 | 159 | // Check if spider is within branch X range |
| 160 | - let branchStart = Math.min(branch.startX, branch.endX) | |
| 161 | - let branchEnd = Math.max(branch.startX, branch.endX) | |
| 160 | + let branchStart = Math.min(branch.startX, branch.endX); | |
| 161 | + let branchEnd = Math.max(branch.startX, branch.endX); | |
| 162 | 162 | |
| 163 | 163 | // Since the branch angle is very small (0.05 radians ≈ 3 degrees), |
| 164 | 164 | // we can use a simpler approximation |
| 165 | 165 | if (this.pos.x >= branchStart - 10 && this.pos.x <= branchEnd + 10) { |
| 166 | 166 | // Calculate position along branch (0 to 1) |
| 167 | - let t = (this.pos.x - branchStart) / (branchEnd - branchStart) | |
| 168 | - t = constrain(t, 0, 1) | |
| 167 | + let t = (this.pos.x - branchStart) / (branchEnd - branchStart); | |
| 168 | + t = constrain(t, 0, 1); | |
| 169 | 169 | |
| 170 | 170 | // Branch visual thickness tapers from full at start to 35% at end |
| 171 | 171 | // This matches exactly how it's drawn in the bezier curves |
@@ -173,18 +173,18 @@ class Spider { | ||
| 173 | 173 | branch.thickness * 0.9, |
| 174 | 174 | branch.thickness * 0.35, |
| 175 | 175 | t |
| 176 | - ) | |
| 176 | + ); | |
| 177 | 177 | |
| 178 | 178 | // The branch is drawn centered at branch.y |
| 179 | 179 | // With small angle approximation: the top of the branch is at |
| 180 | - let branchSurfaceY = branch.y - branchTopThickness | |
| 180 | + let branchSurfaceY = branch.y - branchTopThickness; | |
| 181 | 181 | |
| 182 | 182 | // Add slight angle correction (for small angles, tan ≈ sin ≈ angle in radians) |
| 183 | - let angleCorrection = (this.pos.x - branchStart) * branch.angle | |
| 184 | - branchSurfaceY += angleCorrection | |
| 183 | + let angleCorrection = (this.pos.x - branchStart) * branch.angle; | |
| 184 | + branchSurfaceY += angleCorrection; | |
| 185 | 185 | |
| 186 | 186 | // Check if spider is crossing the branch from above |
| 187 | - let prevY = this.pos.y - this.vel.y | |
| 187 | + let prevY = this.pos.y - this.vel.y; | |
| 188 | 188 | |
| 189 | 189 | if ( |
| 190 | 190 | prevY <= branchSurfaceY && // Was above |
@@ -194,9 +194,9 @@ class Spider { | ||
| 194 | 194 | // Not too far below |
| 195 | 195 | |
| 196 | 196 | // Place spider on the branch surface |
| 197 | - this.pos.y = branchSurfaceY - this.radius | |
| 198 | - this.land() | |
| 199 | - this.attachedObstacle = null | |
| 197 | + this.pos.y = branchSurfaceY - this.radius; | |
| 198 | + this.land(); | |
| 199 | + this.attachedObstacle = null; | |
| 200 | 200 | } |
| 201 | 201 | } |
| 202 | 202 | } |
@@ -204,44 +204,44 @@ class Spider { | ||
| 204 | 204 | // Check obstacle collisions |
| 205 | 205 | for (let obstacle of obstacles) { |
| 206 | 206 | if (this.checkObstacleCollision(obstacle)) { |
| 207 | - this.landOnObstacle(obstacle) | |
| 207 | + this.landOnObstacle(obstacle); | |
| 208 | 208 | } |
| 209 | 209 | } |
| 210 | 210 | |
| 211 | 211 | // Check web strand collisions |
| 212 | 212 | for (let strand of webStrands) { |
| 213 | - if (strand === currentStrand) continue | |
| 213 | + if (strand === currentStrand) continue; | |
| 214 | 214 | |
| 215 | 215 | if (this.isAirborne && this.checkStrandCollision(strand)) { |
| 216 | - this.landOnStrand(strand) | |
| 216 | + this.landOnStrand(strand); | |
| 217 | 217 | } |
| 218 | 218 | } |
| 219 | 219 | |
| 220 | 220 | // Check food box collisions |
| 221 | 221 | for (let i = foodBoxes.length - 1; i >= 0; i--) { |
| 222 | - let box = foodBoxes[i] | |
| 222 | + let box = foodBoxes[i]; | |
| 223 | 223 | if ( |
| 224 | 224 | dist(this.pos.x, this.pos.y, box.pos.x, box.pos.y) < |
| 225 | 225 | this.radius + box.radius |
| 226 | 226 | ) { |
| 227 | - box.collect() | |
| 228 | - foodBoxes.splice(i, 1) | |
| 227 | + box.collect(); | |
| 228 | + foodBoxes.splice(i, 1); | |
| 229 | 229 | } |
| 230 | 230 | } |
| 231 | 231 | } |
| 232 | 232 | |
| 233 | - checkObstacleCollision (obstacle) { | |
| 234 | - let d = dist(this.pos.x, this.pos.y, obstacle.x, obstacle.y) | |
| 235 | - return d < this.radius + obstacle.radius | |
| 233 | + checkObstacleCollision(obstacle) { | |
| 234 | + let d = dist(this.pos.x, this.pos.y, obstacle.x, obstacle.y); | |
| 235 | + return d < this.radius + obstacle.radius; | |
| 236 | 236 | } |
| 237 | 237 | |
| 238 | - checkStrandCollision (strand) { | |
| 238 | + checkStrandCollision(strand) { | |
| 239 | 239 | if (!strand || !strand.start || !strand.end) return false; |
| 240 | 240 | let d = this.pointToLineDistance(this.pos, strand.start, strand.end); |
| 241 | 241 | return d < this.radius + 2; |
| 242 | 242 | } |
| 243 | 243 | |
| 244 | - pointToLineDistance (point, lineStart, lineEnd) { | |
| 244 | + pointToLineDistance(point, lineStart, lineEnd) { | |
| 245 | 245 | // Guard nulls |
| 246 | 246 | if (!lineStart || !lineEnd) { |
| 247 | 247 | return Infinity; |
@@ -259,18 +259,18 @@ class Spider { | ||
| 259 | 259 | return p5.Vector.dist(point, closestPoint); |
| 260 | 260 | } |
| 261 | 261 | |
| 262 | - landOnObstacle (obstacle) { | |
| 262 | + landOnObstacle(obstacle) { | |
| 263 | 263 | // Only land if we're actually airborne |
| 264 | 264 | if (!this.isAirborne) return; |
| 265 | 265 | |
| 266 | - let angle = atan2(this.pos.y - obstacle.y, this.pos.x - obstacle.x) | |
| 267 | - this.pos.x = obstacle.x + cos(angle) * (obstacle.radius + this.radius) | |
| 268 | - this.pos.y = obstacle.y + sin(angle) * (obstacle.radius + this.radius) | |
| 269 | - this.attachedObstacle = obstacle // Track which obstacle we're on | |
| 270 | - this.land() | |
| 266 | + let angle = atan2(this.pos.y - obstacle.y, this.pos.x - obstacle.x); | |
| 267 | + this.pos.x = obstacle.x + cos(angle) * (obstacle.radius + this.radius); | |
| 268 | + this.pos.y = obstacle.y + sin(angle) * (obstacle.radius + this.radius); | |
| 269 | + this.attachedObstacle = obstacle; // Track which obstacle we're on | |
| 270 | + this.land(); | |
| 271 | 271 | } |
| 272 | 272 | |
| 273 | - landOnStrand (strand) { | |
| 273 | + landOnStrand(strand) { | |
| 274 | 274 | // Only land if we're actually airborne |
| 275 | 275 | if (!this.isAirborne) return; |
| 276 | 276 | if (!strand || !strand.start || !strand.end) return; |
@@ -290,10 +290,10 @@ class Spider { | ||
| 290 | 290 | this.land(); |
| 291 | 291 | } |
| 292 | 292 | |
| 293 | - land () { | |
| 294 | - this.vel.mult(0) | |
| 295 | - this.isAirborne = false | |
| 296 | - this.canJump = true | |
| 293 | + land() { | |
| 294 | + this.vel.mult(0); | |
| 295 | + this.isAirborne = false; | |
| 296 | + this.canJump = true; | |
| 297 | 297 | |
| 298 | 298 | if (currentStrand && isDeployingWeb && (spacePressed || touchHolding)) { |
| 299 | 299 | // Ensure the strand has a valid end and a final node on landing |
@@ -306,151 +306,151 @@ class Spider { | ||
| 306 | 306 | webNodes.push(new WebNode(this.pos.x, this.pos.y)); |
| 307 | 307 | } |
| 308 | 308 | |
| 309 | - currentStrand = null | |
| 310 | - isDeployingWeb = false | |
| 309 | + currentStrand = null; | |
| 310 | + isDeployingWeb = false; | |
| 311 | 311 | } |
| 312 | 312 | |
| 313 | - display () { | |
| 314 | - push() | |
| 315 | - translate(this.pos.x, this.pos.y) | |
| 313 | + display() { | |
| 314 | + push(); | |
| 315 | + translate(this.pos.x, this.pos.y); | |
| 316 | 316 | |
| 317 | 317 | if (isMunching && this.munchCooldown > 15) { |
| 318 | - push() | |
| 319 | - fill(255, 100, 100, 150) | |
| 320 | - noStroke() | |
| 321 | - let munchSize = 15 + sin(frameCount * 0.5) * 5 | |
| 322 | - arc(0, 0, munchSize, munchSize, 0, PI + HALF_PI, PIE) | |
| 323 | - pop() | |
| 318 | + push(); | |
| 319 | + fill(255, 100, 100, 150); | |
| 320 | + noStroke(); | |
| 321 | + let munchSize = 15 + sin(frameCount * 0.5) * 5; | |
| 322 | + arc(0, 0, munchSize, munchSize, 0, PI + HALF_PI, PIE); | |
| 323 | + pop(); | |
| 324 | 324 | } |
| 325 | 325 | |
| 326 | - fill(20) | |
| 327 | - stroke(0) | |
| 328 | - strokeWeight(1) | |
| 329 | - ellipse(0, 0, this.radius * 2) | |
| 326 | + fill(20); | |
| 327 | + stroke(0); | |
| 328 | + strokeWeight(1); | |
| 329 | + ellipse(0, 0, this.radius * 2); | |
| 330 | 330 | |
| 331 | - fill(40) | |
| 332 | - noStroke() | |
| 333 | - ellipse(0, -2, this.radius * 1.2, this.radius * 1.5) | |
| 331 | + fill(40); | |
| 332 | + noStroke(); | |
| 333 | + ellipse(0, -2, this.radius * 1.2, this.radius * 1.5); | |
| 334 | 334 | |
| 335 | 335 | if (gamePhase === 'NIGHT') { |
| 336 | - fill(255, 100, 100) | |
| 336 | + fill(255, 100, 100); | |
| 337 | 337 | } else { |
| 338 | - fill(255, 0, 0) | |
| 338 | + fill(255, 0, 0); | |
| 339 | 339 | } |
| 340 | - ellipse(-3, -3, 3) | |
| 341 | - ellipse(3, -3, 3) | |
| 340 | + ellipse(-3, -3, 3); | |
| 341 | + ellipse(3, -3, 3); | |
| 342 | 342 | |
| 343 | - stroke(0) | |
| 344 | - strokeWeight(1.5) | |
| 343 | + stroke(0); | |
| 344 | + strokeWeight(1.5); | |
| 345 | 345 | for (let i = 0; i < 4; i++) { |
| 346 | - let angle = PI / 6 + (i * PI) / 8 | |
| 347 | - line(0, 0, cos(angle) * 12, sin(angle) * 8) | |
| 348 | - line(0, 0, -cos(angle) * 12, sin(angle) * 8) | |
| 346 | + let angle = PI / 6 + (i * PI) / 8; | |
| 347 | + line(0, 0, cos(angle) * 12, sin(angle) * 8); | |
| 348 | + line(0, 0, -cos(angle) * 12, sin(angle) * 8); | |
| 349 | 349 | } |
| 350 | 350 | |
| 351 | 351 | if (webSilk < 20) { |
| 352 | - fill(255, 100, 100, 150 + sin(frameCount * 0.2) * 50) | |
| 353 | - noStroke() | |
| 354 | - ellipse(0, -15, 8) | |
| 352 | + fill(255, 100, 100, 150 + sin(frameCount * 0.2) * 50); | |
| 353 | + noStroke(); | |
| 354 | + ellipse(0, -15, 8); | |
| 355 | 355 | } |
| 356 | 356 | |
| 357 | - pop() | |
| 357 | + pop(); | |
| 358 | 358 | } |
| 359 | 359 | } |
| 360 | 360 | |
| 361 | 361 | class Fly { |
| 362 | - constructor () { | |
| 362 | + constructor() { | |
| 363 | 363 | if (random() < 0.5) { |
| 364 | 364 | this.pos = createVector( |
| 365 | 365 | random() < 0.5 ? -20 : width + 20, |
| 366 | 366 | random(50, height - 100) |
| 367 | - ) | |
| 367 | + ); | |
| 368 | 368 | } else { |
| 369 | - this.pos = createVector(random(width), random() < 0.5 ? -20 : height + 20) | |
| 369 | + this.pos = createVector(random(width), random() < 0.5 ? -20 : height + 20); | |
| 370 | 370 | } |
| 371 | 371 | |
| 372 | - this.vel = createVector(random(-2, 2), random(-1, 1)) | |
| 373 | - this.acc = createVector(0, 0) | |
| 374 | - this.radius = 4 | |
| 375 | - this.caught = false | |
| 376 | - this.stuck = false | |
| 377 | - this.wingPhase = random(TWO_PI) | |
| 378 | - this.wanderAngle = random(TWO_PI) | |
| 379 | - this.glowIntensity = random(150, 255) | |
| 380 | - this.touchedStrands = new Set() | |
| 381 | - this.slowedBy = new Set() // Track which strands are slowing us | |
| 382 | - this.baseSpeed = 3 | |
| 383 | - this.currentSpeed = this.baseSpeed | |
| 372 | + this.vel = createVector(random(-2, 2), random(-1, 1)); | |
| 373 | + this.acc = createVector(0, 0); | |
| 374 | + this.radius = 4; | |
| 375 | + this.caught = false; | |
| 376 | + this.stuck = false; | |
| 377 | + this.wingPhase = random(TWO_PI); | |
| 378 | + this.wanderAngle = random(TWO_PI); | |
| 379 | + this.glowIntensity = random(150, 255); | |
| 380 | + this.touchedStrands = new Set(); | |
| 381 | + this.slowedBy = new Set(); // Track which strands are slowing us | |
| 382 | + this.baseSpeed = 3; | |
| 383 | + this.currentSpeed = this.baseSpeed; | |
| 384 | 384 | } |
| 385 | 385 | |
| 386 | - update () { | |
| 386 | + update() { | |
| 387 | 387 | if (this.stuck) { |
| 388 | 388 | // If stuck, check if we need to move with a drifting web |
| 389 | - this.updatePositionOnWeb() | |
| 390 | - return | |
| 389 | + this.updatePositionOnWeb(); | |
| 390 | + return; | |
| 391 | 391 | } |
| 392 | 392 | |
| 393 | 393 | if (this.caught) { |
| 394 | - this.vel.mult(0.95) | |
| 394 | + this.vel.mult(0.95); | |
| 395 | 395 | if (this.vel.mag() < 0.1) { |
| 396 | - this.stuck = true | |
| 397 | - fliesCaught++ | |
| 398 | - webSilk = min(webSilk + 5, maxWebSilk) | |
| 396 | + this.stuck = true; | |
| 397 | + fliesCaught++; | |
| 398 | + webSilk = min(webSilk + 5, maxWebSilk); | |
| 399 | 399 | } |
| 400 | 400 | // While caught but not yet stuck, also follow the web |
| 401 | - this.updatePositionOnWeb() | |
| 402 | - return | |
| 401 | + this.updatePositionOnWeb(); | |
| 402 | + return; | |
| 403 | 403 | } |
| 404 | 404 | |
| 405 | - this.wanderAngle += random(-0.3, 0.3) | |
| 406 | - let wanderForce = createVector(cos(this.wanderAngle), sin(this.wanderAngle)) | |
| 407 | - wanderForce.mult(0.1) | |
| 408 | - this.acc.add(wanderForce) | |
| 405 | + this.wanderAngle += random(-0.3, 0.3); | |
| 406 | + let wanderForce = createVector(cos(this.wanderAngle), sin(this.wanderAngle)); | |
| 407 | + wanderForce.mult(0.1); | |
| 408 | + this.acc.add(wanderForce); | |
| 409 | 409 | |
| 410 | 410 | // Apply current speed (which may be slowed) |
| 411 | - this.vel.add(this.acc) | |
| 412 | - this.vel.limit(this.currentSpeed) | |
| 413 | - this.pos.add(this.vel) | |
| 414 | - this.acc.mult(0) | |
| 411 | + this.vel.add(this.acc); | |
| 412 | + this.vel.limit(this.currentSpeed); | |
| 413 | + this.pos.add(this.vel); | |
| 414 | + this.acc.mult(0); | |
| 415 | 415 | |
| 416 | - if (this.pos.x < -30) this.pos.x = width + 30 | |
| 417 | - if (this.pos.x > width + 30) this.pos.x = -30 | |
| 418 | - if (this.pos.y < -30) this.pos.y = height + 30 | |
| 419 | - if (this.pos.y > height + 30) this.pos.y = -30 | |
| 416 | + if (this.pos.x < -30) this.pos.x = width + 30; | |
| 417 | + if (this.pos.x > width + 30) this.pos.x = -30; | |
| 418 | + if (this.pos.y < -30) this.pos.y = height + 30; | |
| 419 | + if (this.pos.y > height + 30) this.pos.y = -30; | |
| 420 | 420 | |
| 421 | 421 | // Check web collisions |
| 422 | - this.checkWebCollisions() | |
| 422 | + this.checkWebCollisions(); | |
| 423 | 423 | } |
| 424 | 424 | |
| 425 | 425 | updatePositionOnWeb() { |
| 426 | 426 | // Find the web strand(s) this fly is attached to |
| 427 | 427 | for (let strand of webStrands) { |
| 428 | - if (strand.broken) continue | |
| 428 | + if (strand.broken) continue; | |
| 429 | 429 | |
| 430 | 430 | // Check if fly is on this strand |
| 431 | - let closestPoint = null | |
| 432 | - let closestDistance = Infinity | |
| 431 | + let closestPoint = null; | |
| 432 | + let closestDistance = Infinity; | |
| 433 | 433 | |
| 434 | 434 | if (strand.path && strand.path.length > 1) { |
| 435 | 435 | for (let i = 0; i < strand.path.length - 1; i++) { |
| 436 | - let p1 = strand.path[i] | |
| 437 | - let p2 = strand.path[i + 1] | |
| 436 | + let p1 = strand.path[i]; | |
| 437 | + let p2 = strand.path[i + 1]; | |
| 438 | 438 | |
| 439 | 439 | // Find closest point on this segment |
| 440 | - let line = p5.Vector.sub(p2, p1) | |
| 441 | - let lineLength = line.mag() | |
| 442 | - if (lineLength === 0) continue | |
| 443 | - line.normalize() | |
| 440 | + let line = p5.Vector.sub(p2, p1); | |
| 441 | + let lineLength = line.mag(); | |
| 442 | + if (lineLength === 0) continue; | |
| 443 | + line.normalize(); | |
| 444 | 444 | |
| 445 | - let pointToStart = p5.Vector.sub(this.pos, p1) | |
| 446 | - let projLength = constrain(pointToStart.dot(line), 0, lineLength) | |
| 445 | + let pointToStart = p5.Vector.sub(this.pos, p1); | |
| 446 | + let projLength = constrain(pointToStart.dot(line), 0, lineLength); | |
| 447 | 447 | |
| 448 | - let projPoint = p5.Vector.add(p1, p5.Vector.mult(line, projLength)) | |
| 449 | - let d = p5.Vector.dist(this.pos, projPoint) | |
| 448 | + let projPoint = p5.Vector.add(p1, p5.Vector.mult(line, projLength)); | |
| 449 | + let d = p5.Vector.dist(this.pos, projPoint); | |
| 450 | 450 | |
| 451 | 451 | if (d < closestDistance && d < this.radius + 5) { |
| 452 | - closestDistance = d | |
| 453 | - closestPoint = projPoint | |
| 452 | + closestDistance = d; | |
| 453 | + closestPoint = projPoint; | |
| 454 | 454 | } |
| 455 | 455 | } |
| 456 | 456 | } |
@@ -458,74 +458,74 @@ class Fly { | ||
| 458 | 458 | // If we found a close point on this strand, stick to it |
| 459 | 459 | if (closestPoint) { |
| 460 | 460 | // Move fly to follow the strand's movement |
| 461 | - this.pos.x = closestPoint.x | |
| 462 | - this.pos.y = closestPoint.y | |
| 461 | + this.pos.x = closestPoint.x; | |
| 462 | + this.pos.y = closestPoint.y; | |
| 463 | 463 | |
| 464 | 464 | // Add small vibration when on a moving web |
| 465 | 465 | if (strand.vibration > 0) { |
| 466 | - this.pos.x += random(-1, 1) * strand.vibration * 0.1 | |
| 467 | - this.pos.y += random(-1, 1) * strand.vibration * 0.1 | |
| 466 | + this.pos.x += random(-1, 1) * strand.vibration * 0.1; | |
| 467 | + this.pos.y += random(-1, 1) * strand.vibration * 0.1; | |
| 468 | 468 | } |
| 469 | 469 | } |
| 470 | 470 | } |
| 471 | 471 | } |
| 472 | 472 | |
| 473 | - checkWebCollisions () { | |
| 474 | - let currentlyTouching = new Set() | |
| 473 | + checkWebCollisions() { | |
| 474 | + let currentlyTouching = new Set(); | |
| 475 | 475 | |
| 476 | 476 | for (let strand of webStrands) { |
| 477 | - let touching = false | |
| 477 | + let touching = false; | |
| 478 | 478 | |
| 479 | 479 | // Check collision with strand path |
| 480 | 480 | if (strand.path && strand.path.length > 1) { |
| 481 | 481 | for (let i = 0; i < strand.path.length - 1; i++) { |
| 482 | - let p1 = strand.path[i] | |
| 483 | - let p2 = strand.path[i + 1] | |
| 484 | - let d = this.pointToLineDistance(this.pos, p1, p2) | |
| 482 | + let p1 = strand.path[i]; | |
| 483 | + let p2 = strand.path[i + 1]; | |
| 484 | + let d = this.pointToLineDistance(this.pos, p1, p2); | |
| 485 | 485 | if (d < this.radius + 3) { |
| 486 | - touching = true | |
| 487 | - break | |
| 486 | + touching = true; | |
| 487 | + break; | |
| 488 | 488 | } |
| 489 | 489 | } |
| 490 | 490 | } else if (strand.start && strand.end) { |
| 491 | 491 | // Fallback for strands without path |
| 492 | - let d = this.pointToLineDistance(this.pos, strand.start, strand.end) | |
| 492 | + let d = this.pointToLineDistance(this.pos, strand.start, strand.end); | |
| 493 | 493 | if (d < this.radius + 3) { |
| 494 | - touching = true | |
| 494 | + touching = true; | |
| 495 | 495 | } |
| 496 | 496 | } |
| 497 | 497 | |
| 498 | 498 | if (touching) { |
| 499 | - currentlyTouching.add(strand) | |
| 499 | + currentlyTouching.add(strand); | |
| 500 | 500 | |
| 501 | 501 | // If this is a new strand we're touching |
| 502 | 502 | if (!this.touchedStrands.has(strand)) { |
| 503 | - this.touchedStrands.add(strand) | |
| 503 | + this.touchedStrands.add(strand); | |
| 504 | 504 | |
| 505 | 505 | // Vibrate the web when first touching |
| 506 | - strand.vibrate(3) | |
| 506 | + strand.vibrate(3); | |
| 507 | 507 | |
| 508 | 508 | // First strand slows us down |
| 509 | 509 | if (this.touchedStrands.size === 1) { |
| 510 | - this.currentSpeed = this.baseSpeed * 0.4 // Slow to 40% speed | |
| 511 | - this.slowedBy.add(strand) | |
| 510 | + this.currentSpeed = this.baseSpeed * 0.4; // Slow to 40% speed | |
| 511 | + this.slowedBy.add(strand); | |
| 512 | 512 | |
| 513 | 513 | // Visual feedback - yellow particles for slowing |
| 514 | 514 | for (let j = 0; j < 3; j++) { |
| 515 | - let p = new Particle(this.pos.x, this.pos.y) | |
| 516 | - p.color = color(255, 255, 0, 150) | |
| 517 | - p.vel = createVector(random(-1, 1), random(-1, 1)) | |
| 518 | - p.size = 3 | |
| 519 | - particles.push(p) | |
| 515 | + let p = new Particle(this.pos.x, this.pos.y); | |
| 516 | + p.color = color(255, 255, 0, 150); | |
| 517 | + p.vel = createVector(random(-1, 1), random(-1, 1)); | |
| 518 | + p.size = 3; | |
| 519 | + particles.push(p); | |
| 520 | 520 | } |
| 521 | 521 | } |
| 522 | 522 | // Second strand catches us |
| 523 | 523 | else if (this.touchedStrands.size >= 2 && !this.caught) { |
| 524 | - this.caught = true | |
| 525 | - this.currentSpeed = 0 | |
| 524 | + this.caught = true; | |
| 525 | + this.currentSpeed = 0; | |
| 526 | 526 | |
| 527 | 527 | // Stronger vibration when caught |
| 528 | - strand.vibrate(8) | |
| 528 | + strand.vibrate(8); | |
| 529 | 529 | |
| 530 | 530 | // Also vibrate nearby strands |
| 531 | 531 | for (let otherStrand of webStrands) { |
@@ -536,28 +536,28 @@ class Fly { | ||
| 536 | 536 | otherStrand.start.y, |
| 537 | 537 | touchedStrand.start.x, |
| 538 | 538 | touchedStrand.start.y |
| 539 | - ) | |
| 539 | + ); | |
| 540 | 540 | let d2 = dist( |
| 541 | 541 | otherStrand.start.x, |
| 542 | 542 | otherStrand.start.y, |
| 543 | 543 | touchedStrand.end.x, |
| 544 | 544 | touchedStrand.end.y |
| 545 | - ) | |
| 545 | + ); | |
| 546 | 546 | let d3 = dist( |
| 547 | 547 | otherStrand.end.x, |
| 548 | 548 | otherStrand.end.y, |
| 549 | 549 | touchedStrand.start.x, |
| 550 | 550 | touchedStrand.start.y |
| 551 | - ) | |
| 551 | + ); | |
| 552 | 552 | let d4 = dist( |
| 553 | 553 | otherStrand.end.x, |
| 554 | 554 | otherStrand.end.y, |
| 555 | 555 | touchedStrand.end.x, |
| 556 | 556 | touchedStrand.end.y |
| 557 | - ) | |
| 557 | + ); | |
| 558 | 558 | if (min(d1, d2, d3, d4) < 50) { |
| 559 | - otherStrand.vibrate(2) | |
| 560 | - break | |
| 559 | + otherStrand.vibrate(2); | |
| 560 | + break; | |
| 561 | 561 | } |
| 562 | 562 | } |
| 563 | 563 | } |
@@ -565,10 +565,10 @@ class Fly { | ||
| 565 | 565 | |
| 566 | 566 | // Create caught particles |
| 567 | 567 | for (let j = 0; j < 6; j++) { |
| 568 | - let p = new Particle(this.pos.x, this.pos.y) | |
| 569 | - p.color = color(255, 200, 0, 200) | |
| 570 | - p.vel = createVector(random(-2, 2), random(-2, 2)) | |
| 571 | - particles.push(p) | |
| 568 | + let p = new Particle(this.pos.x, this.pos.y); | |
| 569 | + p.color = color(255, 200, 0, 200); | |
| 570 | + p.vel = createVector(random(-2, 2), random(-2, 2)); | |
| 571 | + particles.push(p); | |
| 572 | 572 | } |
| 573 | 573 | } |
| 574 | 574 | } |
@@ -577,190 +577,189 @@ class Fly { | ||
| 577 | 577 | |
| 578 | 578 | // If we're no longer touching strands we were slowed by, speed back up |
| 579 | 579 | if (this.slowedBy.size > 0 && currentlyTouching.size === 0) { |
| 580 | - this.currentSpeed = this.baseSpeed | |
| 581 | - this.slowedBy.clear() | |
| 580 | + this.currentSpeed = this.baseSpeed; | |
| 581 | + this.slowedBy.clear(); | |
| 582 | 582 | } |
| 583 | 583 | } |
| 584 | 584 | |
| 585 | - pointToLineDistance (point, lineStart, lineEnd) { | |
| 586 | - let line = p5.Vector.sub(lineEnd, lineStart) | |
| 587 | - let lineLength = line.mag() | |
| 588 | - line.normalize() | |
| 585 | + pointToLineDistance(point, lineStart, lineEnd) { | |
| 586 | + let line = p5.Vector.sub(lineEnd, lineStart); | |
| 587 | + let lineLength = line.mag(); | |
| 588 | + line.normalize(); | |
| 589 | 589 | |
| 590 | - let pointToStart = p5.Vector.sub(point, lineStart) | |
| 591 | - let projLength = constrain(pointToStart.dot(line), 0, lineLength) | |
| 590 | + let pointToStart = p5.Vector.sub(point, lineStart); | |
| 591 | + let projLength = constrain(pointToStart.dot(line), 0, lineLength); | |
| 592 | 592 | |
| 593 | 593 | let closestPoint = p5.Vector.add( |
| 594 | 594 | lineStart, |
| 595 | 595 | p5.Vector.mult(line, projLength) |
| 596 | - ) | |
| 597 | - return p5.Vector.dist(point, closestPoint) | |
| 596 | + ); | |
| 597 | + return p5.Vector.dist(point, closestPoint); | |
| 598 | 598 | } |
| 599 | 599 | |
| 600 | - display () { | |
| 601 | - push() | |
| 602 | - translate(this.pos.x, this.pos.y) | |
| 600 | + display() { | |
| 601 | + push(); | |
| 602 | + translate(this.pos.x, this.pos.y); | |
| 603 | 603 | |
| 604 | 604 | // Show slowdown effect |
| 605 | 605 | if (this.slowedBy.size > 0 && !this.caught) { |
| 606 | - stroke(255, 255, 0, 100) | |
| 607 | - strokeWeight(1) | |
| 608 | - noFill() | |
| 609 | - ellipse(0, 0, 20) | |
| 606 | + stroke(255, 255, 0, 100); | |
| 607 | + strokeWeight(1); | |
| 608 | + noFill(); | |
| 609 | + ellipse(0, 0, 20); | |
| 610 | 610 | } |
| 611 | 611 | |
| 612 | 612 | if (gamePhase === 'NIGHT') { |
| 613 | - noStroke() | |
| 614 | - fill(255, 255, 150, this.glowIntensity * 0.3) | |
| 615 | - ellipse(0, 0, 30) | |
| 616 | - fill(255, 255, 100, this.glowIntensity * 0.5) | |
| 617 | - ellipse(0, 0, 20) | |
| 613 | + noStroke(); | |
| 614 | + fill(255, 255, 150, this.glowIntensity * 0.3); | |
| 615 | + ellipse(0, 0, 30); | |
| 616 | + fill(255, 255, 100, this.glowIntensity * 0.5); | |
| 617 | + ellipse(0, 0, 20); | |
| 618 | 618 | } |
| 619 | 619 | |
| 620 | - fill(30) | |
| 621 | - stroke(0) | |
| 622 | - strokeWeight(0.5) | |
| 623 | - ellipse(0, 0, this.radius * 2) | |
| 620 | + fill(30); | |
| 621 | + stroke(0); | |
| 622 | + strokeWeight(0.5); | |
| 623 | + ellipse(0, 0, this.radius * 2); | |
| 624 | 624 | |
| 625 | 625 | if (!this.stuck) { |
| 626 | - this.wingPhase += 0.5 | |
| 627 | 626 | // Wing animation slows down when slowed |
| 628 | - let wingSpeed = this.slowedBy.size > 0 ? 0.25 : 0.5 | |
| 629 | - this.wingPhase += wingSpeed | |
| 630 | - let wingSpread = sin(this.wingPhase) * 5 | |
| 631 | - | |
| 632 | - fill(255, 255, 255, 150) | |
| 633 | - noStroke() | |
| 634 | - ellipse(-wingSpread, 0, 6, 4) | |
| 635 | - ellipse(wingSpread, 0, 6, 4) | |
| 627 | + let wingSpeed = this.slowedBy.size > 0 ? 0.25 : 0.5; | |
| 628 | + this.wingPhase += wingSpeed; | |
| 629 | + let wingSpread = sin(this.wingPhase) * 5; | |
| 630 | + | |
| 631 | + fill(255, 255, 255, 150); | |
| 632 | + noStroke(); | |
| 633 | + ellipse(-wingSpread, 0, 6, 4); | |
| 634 | + ellipse(wingSpread, 0, 6, 4); | |
| 636 | 635 | } |
| 637 | 636 | |
| 638 | 637 | if (gamePhase === 'NIGHT') { |
| 639 | - fill(255, 255, 100, this.glowIntensity) | |
| 640 | - noStroke() | |
| 641 | - ellipse(0, 2, 3) | |
| 638 | + fill(255, 255, 100, this.glowIntensity); | |
| 639 | + noStroke(); | |
| 640 | + ellipse(0, 2, 3); | |
| 642 | 641 | } |
| 643 | 642 | |
| 644 | - pop() | |
| 643 | + pop(); | |
| 645 | 644 | } |
| 646 | 645 | } |
| 647 | 646 | |
| 648 | 647 | class Obstacle { |
| 649 | - constructor (x, y, radius, type) { | |
| 648 | + constructor(x, y, radius, type) { | |
| 650 | 649 | // Store original position for drift tracking |
| 651 | - this.originalX = x | |
| 652 | - this.originalY = y | |
| 653 | - this.x = x | |
| 654 | - this.y = y | |
| 655 | - this.radius = radius | |
| 656 | - this.type = type || 'leaf' | |
| 657 | - this.rotation = random(TWO_PI) | |
| 658 | - this.leafPoints = [] | |
| 650 | + this.originalX = x; | |
| 651 | + this.originalY = y; | |
| 652 | + this.x = x; | |
| 653 | + this.y = y; | |
| 654 | + this.radius = radius; | |
| 655 | + this.type = type || 'leaf'; | |
| 656 | + this.rotation = random(TWO_PI); | |
| 657 | + this.leafPoints = []; | |
| 659 | 658 | |
| 660 | 659 | // Movement properties for all types |
| 661 | - this.bobOffset = random(TWO_PI) | |
| 662 | - this.bobSpeed = random(0.02, 0.04) | |
| 663 | - this.bobAmount = 0 | |
| 660 | + this.bobOffset = random(TWO_PI); | |
| 661 | + this.bobSpeed = random(0.02, 0.04); | |
| 662 | + this.bobAmount = 0; | |
| 664 | 663 | |
| 665 | 664 | // Type-specific initialization |
| 666 | 665 | if (this.type === 'balloon') { |
| 667 | - this.bobAmount = 8 // Balloons bob more | |
| 666 | + this.bobAmount = 8; // Balloons bob more | |
| 668 | 667 | this.balloonColors = [ |
| 669 | 668 | color(255, 100, 100), // Red |
| 670 | 669 | color(100, 200, 255), // Blue |
| 671 | 670 | color(255, 200, 100) // Yellow |
| 672 | - ] | |
| 673 | - this.balloonColor = random(this.balloonColors) | |
| 674 | - this.stringWave = 0 | |
| 675 | - this.antLegPhase = random(TWO_PI) | |
| 671 | + ]; | |
| 672 | + this.balloonColor = random(this.balloonColors); | |
| 673 | + this.stringWave = 0; | |
| 674 | + this.antLegPhase = random(TWO_PI); | |
| 676 | 675 | |
| 677 | 676 | } else if (this.type === 'beetle') { |
| 678 | - this.bobAmount = 4 | |
| 679 | - this.driftSpeed = random(0.15, 0.35) | |
| 680 | - this.driftAngle = random(TWO_PI) | |
| 681 | - this.driftChangeRate = random(0.005, 0.015) | |
| 682 | - this.wingPhase = random(TWO_PI) | |
| 677 | + this.bobAmount = 4; | |
| 678 | + this.driftSpeed = random(0.15, 0.35); | |
| 679 | + this.driftAngle = random(TWO_PI); | |
| 680 | + this.driftChangeRate = random(0.005, 0.015); | |
| 681 | + this.wingPhase = random(TWO_PI); | |
| 683 | 682 | this.beetleColor = random() < 0.5 ? |
| 684 | 683 | color(20, 60, 20) : // Dark green |
| 685 | - color(40, 20, 60) // Purple | |
| 686 | - this.driftDistance = 0 // Track total drift | |
| 684 | + color(40, 20, 60); // Purple | |
| 685 | + this.driftDistance = 0; // Track total drift | |
| 687 | 686 | |
| 688 | 687 | } else if (this.type === 'leaf') { |
| 689 | - this.bobAmount = 2 // Leaves bob slightly | |
| 690 | - let numPoints = 8 | |
| 688 | + this.bobAmount = 2; // Leaves bob slightly | |
| 689 | + let numPoints = 8; | |
| 691 | 690 | for (let i = 0; i < numPoints; i++) { |
| 692 | - let angle = (TWO_PI / numPoints) * i | |
| 693 | - let r = radius * random(0.7, 1.2) | |
| 694 | - if (i === 0 || i === numPoints / 2) r = radius * 1.3 | |
| 695 | - this.leafPoints.push({ angle: angle, radius: r }) | |
| 691 | + let angle = (TWO_PI / numPoints) * i; | |
| 692 | + let r = radius * random(0.7, 1.2); | |
| 693 | + if (i === 0 || i === numPoints / 2) r = radius * 1.3; | |
| 694 | + this.leafPoints.push({ angle: angle, radius: r }); | |
| 696 | 695 | } |
| 697 | 696 | } else if (this.type === 'branch') { |
| 698 | 697 | // Keep for backwards compatibility |
| 699 | - this.bobAmount = 0 | |
| 698 | + this.bobAmount = 0; | |
| 700 | 699 | } |
| 701 | 700 | } |
| 702 | 701 | |
| 703 | 702 | update() { |
| 704 | 703 | // Bobbing motion for all types |
| 705 | - let bob = sin(frameCount * this.bobSpeed + this.bobOffset) * this.bobAmount | |
| 706 | - this.y = this.originalY + bob | |
| 704 | + let bob = sin(frameCount * this.bobSpeed + this.bobOffset) * this.bobAmount; | |
| 705 | + this.y = this.originalY + bob; | |
| 707 | 706 | |
| 708 | 707 | // Beetle-specific drift |
| 709 | 708 | if (this.type === 'beetle') { |
| 710 | 709 | // Store initial position if not set |
| 711 | 710 | if (!this.initialX) { |
| 712 | - this.initialX = this.x | |
| 713 | - this.initialY = this.y | |
| 711 | + this.initialX = this.x; | |
| 712 | + this.initialY = this.y; | |
| 714 | 713 | } |
| 715 | 714 | |
| 716 | 715 | // Slowly change drift direction using Perlin noise |
| 717 | - this.driftAngle += (noise(frameCount * this.driftChangeRate, this.originalX * 0.01) - 0.5) * 0.1 | |
| 716 | + this.driftAngle += (noise(frameCount * this.driftChangeRate, this.originalX * 0.01) - 0.5) * 0.1; | |
| 718 | 717 | |
| 719 | 718 | // Apply drift to original position |
| 720 | - this.originalX += cos(this.driftAngle) * this.driftSpeed | |
| 721 | - this.originalY += sin(this.driftAngle) * this.driftSpeed * 0.5 | |
| 719 | + this.originalX += cos(this.driftAngle) * this.driftSpeed; | |
| 720 | + this.originalY += sin(this.driftAngle) * this.driftSpeed * 0.5; | |
| 722 | 721 | |
| 723 | 722 | // Calculate total drift distance from initial position |
| 724 | - this.driftDistance = dist(this.originalX, this.originalY, this.initialX, this.initialY) | |
| 723 | + this.driftDistance = dist(this.originalX, this.originalY, this.initialX, this.initialY); | |
| 725 | 724 | |
| 726 | 725 | // Keep beetles on screen with soft boundaries |
| 727 | 726 | if (this.originalX < 80) { |
| 728 | - this.driftAngle = random(-PI/4, PI/4) | |
| 729 | - this.originalX = 80 | |
| 727 | + this.driftAngle = random(-PI/4, PI/4); | |
| 728 | + this.originalX = 80; | |
| 730 | 729 | } |
| 731 | 730 | if (this.originalX > width - 80) { |
| 732 | - this.driftAngle = random(3*PI/4, 5*PI/4) | |
| 733 | - this.originalX = width - 80 | |
| 731 | + this.driftAngle = random(3*PI/4, 5*PI/4); | |
| 732 | + this.originalX = width - 80; | |
| 734 | 733 | } |
| 735 | 734 | if (this.originalY < 80) { |
| 736 | - this.driftAngle = random(-3*PI/4, -PI/4) | |
| 737 | - this.originalY = 80 | |
| 735 | + this.driftAngle = random(-3*PI/4, -PI/4); | |
| 736 | + this.originalY = 80; | |
| 738 | 737 | } |
| 739 | 738 | if (this.originalY > height - 150) { |
| 740 | - this.driftAngle = random(PI/4, 3*PI/4) | |
| 741 | - this.originalY = height - 150 | |
| 739 | + this.driftAngle = random(PI/4, 3*PI/4); | |
| 740 | + this.originalY = height - 150; | |
| 742 | 741 | } |
| 743 | 742 | |
| 744 | 743 | // Update actual position (with bob already applied to y) |
| 745 | - this.x = this.originalX | |
| 744 | + this.x = this.originalX; | |
| 746 | 745 | |
| 747 | 746 | // Check if beetle has drifted too far and break attached strands |
| 748 | 747 | if (this.driftDistance > 100) { |
| 749 | - this.breakAttachedStrands() | |
| 748 | + this.breakAttachedStrands(); | |
| 750 | 749 | } |
| 751 | 750 | } |
| 752 | 751 | |
| 753 | 752 | // Update animation phases |
| 754 | 753 | if (this.type === 'balloon') { |
| 755 | - this.stringWave = sin(frameCount * 0.05 + this.bobOffset) * 0.1 | |
| 756 | - this.antLegPhase += 0.1 | |
| 754 | + this.stringWave = sin(frameCount * 0.05 + this.bobOffset) * 0.1; | |
| 755 | + this.antLegPhase += 0.1; | |
| 757 | 756 | } else if (this.type === 'beetle') { |
| 758 | - this.wingPhase += 0.15 | |
| 757 | + this.wingPhase += 0.15; | |
| 759 | 758 | } |
| 760 | 759 | |
| 761 | 760 | // For all moving obstacles, update any attached web strands |
| 762 | 761 | if (this.bobAmount > 0 || this.type === 'beetle') { |
| 763 | - this.updateAttachedStrands() | |
| 762 | + this.updateAttachedStrands(); | |
| 764 | 763 | } |
| 765 | 764 | } |
| 766 | 765 | |
@@ -769,21 +768,21 @@ class Obstacle { | ||
| 769 | 768 | for (let strand of webStrands) { |
| 770 | 769 | // Check if strand starts at this obstacle |
| 771 | 770 | if (dist(strand.start.x, strand.start.y, this.x, this.y) < this.radius + 10) { |
| 772 | - strand.start.x = this.x | |
| 773 | - strand.start.y = this.y | |
| 771 | + strand.start.x = this.x; | |
| 772 | + strand.start.y = this.y; | |
| 774 | 773 | if (strand.path && strand.path.length > 0) { |
| 775 | - strand.path[0].x = this.x | |
| 776 | - strand.path[0].y = this.y | |
| 774 | + strand.path[0].x = this.x; | |
| 775 | + strand.path[0].y = this.y; | |
| 777 | 776 | } |
| 778 | 777 | } |
| 779 | 778 | |
| 780 | 779 | // Check if strand ends at this obstacle |
| 781 | 780 | if (strand.end && dist(strand.end.x, strand.end.y, this.x, this.y) < this.radius + 10) { |
| 782 | - strand.end.x = this.x | |
| 783 | - strand.end.y = this.y | |
| 781 | + strand.end.x = this.x; | |
| 782 | + strand.end.y = this.y; | |
| 784 | 783 | if (strand.path && strand.path.length > 0) { |
| 785 | - strand.path[strand.path.length - 1].x = this.x | |
| 786 | - strand.path[strand.path.length - 1].y = this.y | |
| 784 | + strand.path[strand.path.length - 1].x = this.x; | |
| 785 | + strand.path[strand.path.length - 1].y = this.y; | |
| 787 | 786 | } |
| 788 | 787 | } |
| 789 | 788 | } |
@@ -792,516 +791,516 @@ class Obstacle { | ||
| 792 | 791 | breakAttachedStrands() { |
| 793 | 792 | // Break any strands attached to this beetle that has drifted too far |
| 794 | 793 | for (let strand of webStrands) { |
| 795 | - let attachedToStart = dist(strand.start.x, strand.start.y, this.x, this.y) < this.radius + 10 | |
| 796 | - let attachedToEnd = strand.end && dist(strand.end.x, strand.end.y, this.x, this.y) < this.radius + 10 | |
| 794 | + let attachedToStart = dist(strand.start.x, strand.start.y, this.x, this.y) < this.radius + 10; | |
| 795 | + let attachedToEnd = strand.end && dist(strand.end.x, strand.end.y, this.x, this.y) < this.radius + 10; | |
| 797 | 796 | |
| 798 | 797 | if (attachedToStart || attachedToEnd) { |
| 799 | 798 | // Mark strand as broken |
| 800 | - strand.broken = true | |
| 799 | + strand.broken = true; | |
| 801 | 800 | |
| 802 | 801 | // Release any flies stuck to this strand |
| 803 | 802 | for (let fly of flies) { |
| 804 | 803 | if (fly.stuck || fly.caught) { |
| 805 | 804 | // Check if fly is touching this breaking strand |
| 806 | - let touchingStrand = false | |
| 805 | + let touchingStrand = false; | |
| 807 | 806 | if (strand.path && strand.path.length > 1) { |
| 808 | - for (let i = 0; i < strand.path.length - 1; i++) { | |
| 809 | - let p1 = strand.path[i] | |
| 810 | - let p2 = strand.path[i + 1] | |
| 811 | - let d = fly.pointToLineDistance(fly.pos, p1, p2) | |
| 807 | + for (let k = 0; k < strand.path.length - 1; k++) { | |
| 808 | + let p1 = strand.path[k]; | |
| 809 | + let p2 = strand.path[k + 1]; | |
| 810 | + let d = fly.pointToLineDistance(fly.pos, p1, p2); | |
| 812 | 811 | if (d < fly.radius + 5) { |
| 813 | - touchingStrand = true | |
| 814 | - break | |
| 812 | + touchingStrand = true; | |
| 813 | + break; | |
| 815 | 814 | } |
| 816 | 815 | } |
| 817 | 816 | } |
| 818 | 817 | |
| 819 | 818 | // If fly was on this strand, release it |
| 820 | 819 | if (touchingStrand) { |
| 821 | - fly.stuck = false | |
| 822 | - fly.caught = false | |
| 823 | - fly.currentSpeed = fly.baseSpeed | |
| 824 | - fly.touchedStrands.clear() | |
| 825 | - fly.slowedBy.clear() | |
| 820 | + fly.stuck = false; | |
| 821 | + fly.caught = false; | |
| 822 | + fly.currentSpeed = fly.baseSpeed; | |
| 823 | + fly.touchedStrands.clear(); | |
| 824 | + fly.slowedBy.clear(); | |
| 826 | 825 | // Give it a little downward velocity to start falling |
| 827 | - fly.vel = createVector(random(-0.5, 0.5), 2) | |
| 826 | + fly.vel = createVector(random(-0.5, 0.5), 2); | |
| 828 | 827 | |
| 829 | 828 | // Create release particles |
| 830 | 829 | for (let j = 0; j < 3; j++) { |
| 831 | - let p = new Particle(fly.pos.x, fly.pos.y) | |
| 832 | - p.color = color(255, 255, 100, 150) | |
| 833 | - p.vel = createVector(random(-1, 1), random(0, 2)) | |
| 834 | - p.size = 2 | |
| 835 | - particles.push(p) | |
| 830 | + let p = new Particle(fly.pos.x, fly.pos.y); | |
| 831 | + p.color = color(255, 255, 100, 150); | |
| 832 | + p.vel = createVector(random(-1, 1), random(0, 2)); | |
| 833 | + p.size = 2; | |
| 834 | + particles.push(p); | |
| 836 | 835 | } |
| 837 | 836 | } |
| 838 | 837 | } |
| 839 | 838 | } |
| 840 | 839 | |
| 841 | 840 | // Create dramatic snap particles |
| 842 | - let snapX = attachedToStart ? strand.start.x : strand.end.x | |
| 843 | - let snapY = attachedToStart ? strand.start.y : strand.end.y | |
| 841 | + let snapX = attachedToStart ? strand.start.x : strand.end.x; | |
| 842 | + let snapY = attachedToStart ? strand.start.y : strand.end.y; | |
| 844 | 843 | |
| 845 | 844 | // Red/pink particles for the snap |
| 846 | 845 | for (let i = 0; i < 8; i++) { |
| 847 | - let p = new Particle(snapX, snapY) | |
| 848 | - p.color = color(255, random(100, 200), random(100, 150)) | |
| 849 | - p.vel = createVector(random(-5, 5), random(-5, 2)) | |
| 850 | - p.size = random(4, 8) | |
| 851 | - particles.push(p) | |
| 846 | + let p = new Particle(snapX, snapY); | |
| 847 | + p.color = color(255, random(100, 200), random(100, 150)); | |
| 848 | + p.vel = createVector(random(-5, 5), random(-5, 2)); | |
| 849 | + p.size = random(4, 8); | |
| 850 | + particles.push(p); | |
| 852 | 851 | } |
| 853 | 852 | |
| 854 | 853 | // White strand particles |
| 855 | 854 | for (let i = 0; i < 4; i++) { |
| 856 | - let p = new Particle(snapX, snapY) | |
| 857 | - p.color = color(255, 255, 255) | |
| 858 | - p.vel = createVector(random(-3, 3), random(-3, 0)) | |
| 859 | - p.size = 3 | |
| 860 | - particles.push(p) | |
| 855 | + let p = new Particle(snapX, snapY); | |
| 856 | + p.color = color(255, 255, 255); | |
| 857 | + p.vel = createVector(random(-3, 3), random(-3, 0)); | |
| 858 | + p.size = 3; | |
| 859 | + particles.push(p); | |
| 861 | 860 | } |
| 862 | 861 | |
| 863 | 862 | // Reset beetle drift after breaking strands |
| 864 | - this.initialX = this.x | |
| 865 | - this.initialY = this.y | |
| 866 | - this.driftDistance = 0 | |
| 863 | + this.initialX = this.x; | |
| 864 | + this.initialY = this.y; | |
| 865 | + this.driftDistance = 0; | |
| 867 | 866 | } |
| 868 | 867 | } |
| 869 | 868 | } |
| 870 | 869 | |
| 871 | - display () { | |
| 872 | - push() | |
| 873 | - translate(this.x, this.y) | |
| 870 | + display() { | |
| 871 | + push(); | |
| 872 | + translate(this.x, this.y); | |
| 874 | 873 | |
| 875 | 874 | if (this.type === 'balloon') { |
| 876 | 875 | // Hot air balloon with canvas texture! |
| 877 | - push() | |
| 876 | + push(); | |
| 878 | 877 | |
| 879 | 878 | // String/rope first (behind balloon) |
| 880 | - stroke(80, 60, 40) | |
| 881 | - strokeWeight(1.5) | |
| 882 | - noFill() | |
| 883 | - beginShape() | |
| 879 | + stroke(80, 60, 40); | |
| 880 | + strokeWeight(1.5); | |
| 881 | + noFill(); | |
| 882 | + beginShape(); | |
| 884 | 883 | for (let i = 0; i <= 10; i++) { |
| 885 | - let t = i / 10 | |
| 886 | - let stringX = sin(t * PI * 2 + this.stringWave) * 3 | |
| 887 | - let stringY = t * 40 + this.radius | |
| 888 | - curveVertex(stringX, stringY) | |
| 884 | + let t = i / 10; | |
| 885 | + let stringX = sin(t * PI * 2 + this.stringWave) * 3; | |
| 886 | + let stringY = t * 40 + this.radius; | |
| 887 | + curveVertex(stringX, stringY); | |
| 889 | 888 | } |
| 890 | - endShape() | |
| 889 | + endShape(); | |
| 891 | 890 | |
| 892 | 891 | // Balloon shadow |
| 893 | - noStroke() | |
| 894 | - fill(0, 0, 0, 30) | |
| 895 | - ellipse(5, 5, this.radius * 2.2, this.radius * 2.5) | |
| 892 | + noStroke(); | |
| 893 | + fill(0, 0, 0, 30); | |
| 894 | + ellipse(5, 5, this.radius * 2.2, this.radius * 2.5); | |
| 896 | 895 | |
| 897 | 896 | // Main balloon with canvas panels |
| 898 | - push() | |
| 897 | + push(); | |
| 899 | 898 | // Draw vertical panels for that classic hot air balloon look |
| 900 | - let numPanels = 8 | |
| 899 | + let numPanels = 8; | |
| 901 | 900 | for (let i = 0; i < numPanels; i++) { |
| 902 | - let angle1 = (TWO_PI / numPanels) * i | |
| 903 | - let angle2 = (TWO_PI / numPanels) * (i + 1) | |
| 901 | + let angle1 = (TWO_PI / numPanels) * i; | |
| 902 | + let angle2 = (TWO_PI / numPanels) * (i + 1); | |
| 904 | 903 | |
| 905 | 904 | // Alternate panel colors for striped effect |
| 906 | 905 | if (i % 2 === 0) { |
| 907 | - fill(red(this.balloonColor), green(this.balloonColor), blue(this.balloonColor), 200) | |
| 906 | + fill(red(this.balloonColor), green(this.balloonColor), blue(this.balloonColor), 200); | |
| 908 | 907 | } else { |
| 909 | 908 | fill( |
| 910 | 909 | red(this.balloonColor) - 30, |
| 911 | 910 | green(this.balloonColor) - 30, |
| 912 | 911 | blue(this.balloonColor) - 30, |
| 913 | 912 | 200 |
| 914 | - ) | |
| 913 | + ); | |
| 915 | 914 | } |
| 916 | 915 | |
| 917 | 916 | // Draw tapered panel (wider at middle, narrow at top/bottom) |
| 918 | - beginShape() | |
| 917 | + beginShape(); | |
| 919 | 918 | // Top point |
| 920 | - vertex(0, -this.radius * 1.2) | |
| 919 | + vertex(0, -this.radius * 1.2); | |
| 921 | 920 | // Upper curve |
| 922 | 921 | bezierVertex( |
| 923 | 922 | cos(angle1) * this.radius * 0.3, -this.radius * 0.9, |
| 924 | 923 | cos(angle1) * this.radius * 0.8, -this.radius * 0.3, |
| 925 | 924 | cos(angle1) * this.radius * 1.1, 0 |
| 926 | - ) | |
| 925 | + ); | |
| 927 | 926 | // Lower curve to bottom |
| 928 | 927 | bezierVertex( |
| 929 | 928 | cos(angle1) * this.radius * 0.9, this.radius * 0.5, |
| 930 | 929 | cos(angle1) * this.radius * 0.4, this.radius * 0.9, |
| 931 | 930 | 0, this.radius * 1.1 |
| 932 | - ) | |
| 931 | + ); | |
| 933 | 932 | // Back up the other side |
| 934 | 933 | bezierVertex( |
| 935 | 934 | cos(angle2) * this.radius * 0.4, this.radius * 0.9, |
| 936 | 935 | cos(angle2) * this.radius * 0.9, this.radius * 0.5, |
| 937 | 936 | cos(angle2) * this.radius * 1.1, 0 |
| 938 | - ) | |
| 937 | + ); | |
| 939 | 938 | bezierVertex( |
| 940 | 939 | cos(angle2) * this.radius * 0.8, -this.radius * 0.3, |
| 941 | 940 | cos(angle2) * this.radius * 0.3, -this.radius * 0.9, |
| 942 | 941 | 0, -this.radius * 1.2 |
| 943 | - ) | |
| 944 | - endShape(CLOSE) | |
| 942 | + ); | |
| 943 | + endShape(CLOSE); | |
| 945 | 944 | } |
| 946 | 945 | |
| 947 | 946 | // Panel seams/ropes |
| 948 | - stroke(60, 40, 20, 100) | |
| 949 | - strokeWeight(0.5) | |
| 947 | + stroke(60, 40, 20, 100); | |
| 948 | + strokeWeight(0.5); | |
| 950 | 949 | for (let i = 0; i < numPanels; i++) { |
| 951 | - let angle = (TWO_PI / numPanels) * i | |
| 950 | + let angle = (TWO_PI / numPanels) * i; | |
| 952 | 951 | // Vertical seam lines |
| 953 | - beginShape() | |
| 954 | - noFill() | |
| 955 | - vertex(0, -this.radius * 1.2) | |
| 952 | + beginShape(); | |
| 953 | + noFill(); | |
| 954 | + vertex(0, -this.radius * 1.2); | |
| 956 | 955 | bezierVertex( |
| 957 | 956 | cos(angle) * this.radius * 0.3, -this.radius * 0.9, |
| 958 | 957 | cos(angle) * this.radius * 0.8, -this.radius * 0.3, |
| 959 | 958 | cos(angle) * this.radius * 1.1, 0 |
| 960 | - ) | |
| 959 | + ); | |
| 961 | 960 | bezierVertex( |
| 962 | 961 | cos(angle) * this.radius * 0.9, this.radius * 0.5, |
| 963 | 962 | cos(angle) * this.radius * 0.4, this.radius * 0.9, |
| 964 | 963 | 0, this.radius * 1.1 |
| 965 | - ) | |
| 966 | - endShape() | |
| 964 | + ); | |
| 965 | + endShape(); | |
| 967 | 966 | } |
| 968 | 967 | |
| 969 | 968 | // Highlight on balloon |
| 970 | - noStroke() | |
| 971 | - fill(255, 255, 255, 80) | |
| 972 | - ellipse(-this.radius * 0.3, -this.radius * 0.5, this.radius * 0.6, this.radius * 0.7) | |
| 973 | - pop() | |
| 969 | + noStroke(); | |
| 970 | + fill(255, 255, 255, 80); | |
| 971 | + ellipse(-this.radius * 0.3, -this.radius * 0.5, this.radius * 0.6, this.radius * 0.7); | |
| 972 | + pop(); | |
| 974 | 973 | |
| 975 | 974 | // FLAME EFFECT! |
| 976 | - push() | |
| 977 | - translate(0, this.radius - 5) | |
| 975 | + push(); | |
| 976 | + translate(0, this.radius - 5); | |
| 978 | 977 | // Flame glow |
| 979 | - noStroke() | |
| 980 | - fill(255, 200, 0, 40 + sin(frameCount * 0.3) * 20) | |
| 981 | - ellipse(0, 0, 25, 25) | |
| 982 | - fill(255, 150, 0, 60 + sin(frameCount * 0.4) * 30) | |
| 983 | - ellipse(0, 0, 15, 18) | |
| 978 | + noStroke(); | |
| 979 | + fill(255, 200, 0, 40 + sin(frameCount * 0.3) * 20); | |
| 980 | + ellipse(0, 0, 25, 25); | |
| 981 | + fill(255, 150, 0, 60 + sin(frameCount * 0.4) * 30); | |
| 982 | + ellipse(0, 0, 15, 18); | |
| 984 | 983 | // Flame itself |
| 985 | - fill(255, 200, 0) | |
| 986 | - push() | |
| 987 | - let flameHeight = 8 + sin(frameCount * 0.5) * 3 | |
| 988 | - translate(0, -2) | |
| 989 | - beginShape() | |
| 990 | - vertex(-3, 0) | |
| 991 | - bezierVertex(-3, -flameHeight * 0.7, -1, -flameHeight, 0, -flameHeight * 1.2) | |
| 992 | - bezierVertex(1, -flameHeight, 3, -flameHeight * 0.7, 3, 0) | |
| 993 | - endShape(CLOSE) | |
| 994 | - fill(255, 255, 200) | |
| 995 | - ellipse(0, -flameHeight * 0.5, 3, 4) | |
| 996 | - pop() | |
| 997 | - pop() | |
| 984 | + fill(255, 200, 0); | |
| 985 | + push(); | |
| 986 | + let flameHeight = 8 + sin(frameCount * 0.5) * 3; | |
| 987 | + translate(0, -2); | |
| 988 | + beginShape(); | |
| 989 | + vertex(-3, 0); | |
| 990 | + bezierVertex(-3, -flameHeight * 0.7, -1, -flameHeight, 0, -flameHeight * 1.2); | |
| 991 | + bezierVertex(1, -flameHeight, 3, -flameHeight * 0.7, 3, 0); | |
| 992 | + endShape(CLOSE); | |
| 993 | + fill(255, 255, 200); | |
| 994 | + ellipse(0, -flameHeight * 0.5, 3, 4); | |
| 995 | + pop(); | |
| 996 | + pop(); | |
| 998 | 997 | |
| 999 | 998 | // Basket |
| 1000 | - push() | |
| 1001 | - translate(0, this.radius + 10) | |
| 1002 | - fill(101, 67, 33) | |
| 1003 | - stroke(80, 50, 20) | |
| 1004 | - strokeWeight(1) | |
| 999 | + push(); | |
| 1000 | + translate(0, this.radius + 10); | |
| 1001 | + fill(101, 67, 33); | |
| 1002 | + stroke(80, 50, 20); | |
| 1003 | + strokeWeight(1); | |
| 1005 | 1004 | // Woven basket shape |
| 1006 | - beginShape() | |
| 1007 | - vertex(-8, 0) | |
| 1008 | - vertex(8, 0) | |
| 1009 | - vertex(6, 10) | |
| 1010 | - vertex(-6, 10) | |
| 1011 | - endShape(CLOSE) | |
| 1005 | + beginShape(); | |
| 1006 | + vertex(-8, 0); | |
| 1007 | + vertex(8, 0); | |
| 1008 | + vertex(6, 10); | |
| 1009 | + vertex(-6, 10); | |
| 1010 | + endShape(CLOSE); | |
| 1012 | 1011 | // Basket weave pattern |
| 1013 | - stroke(80, 50, 20, 150) | |
| 1012 | + stroke(80, 50, 20, 150); | |
| 1014 | 1013 | for (let i = -6; i < 6; i += 2) { |
| 1015 | - line(i, 1, i, 9) | |
| 1014 | + line(i, 1, i, 9); | |
| 1016 | 1015 | } |
| 1017 | 1016 | for (let i = 2; i < 9; i += 2) { |
| 1018 | - line(-6, i, 6, i) | |
| 1017 | + line(-6, i, 6, i); | |
| 1019 | 1018 | } |
| 1020 | 1019 | // Basket rim |
| 1021 | - stroke(60, 40, 20) | |
| 1022 | - strokeWeight(1.5) | |
| 1023 | - line(-8, 0, 8, 0) | |
| 1024 | - pop() | |
| 1020 | + stroke(60, 40, 20); | |
| 1021 | + strokeWeight(1.5); | |
| 1022 | + line(-8, 0, 8, 0); | |
| 1023 | + pop(); | |
| 1025 | 1024 | |
| 1026 | 1025 | // Ant in basket (peeking over edge) |
| 1027 | - push() | |
| 1028 | - translate(0, this.radius + 12) | |
| 1029 | - fill(20) | |
| 1030 | - noStroke() | |
| 1026 | + push(); | |
| 1027 | + translate(0, this.radius + 12); | |
| 1028 | + fill(20); | |
| 1029 | + noStroke(); | |
| 1031 | 1030 | // Just ant head and antennae visible |
| 1032 | - ellipse(0, -2, 6, 4) // Head peeking up | |
| 1031 | + ellipse(0, -2, 6, 4); // Head peeking up | |
| 1033 | 1032 | // Antennae |
| 1034 | - stroke(20) | |
| 1035 | - strokeWeight(0.5) | |
| 1036 | - line(-1, -3, -3, -6) | |
| 1037 | - line(1, -3, 3, -6) | |
| 1033 | + stroke(20); | |
| 1034 | + strokeWeight(0.5); | |
| 1035 | + line(-1, -3, -3, -6); | |
| 1036 | + line(1, -3, 3, -6); | |
| 1038 | 1037 | // Tiny ant arms gripping basket edge |
| 1039 | - strokeWeight(1) | |
| 1040 | - line(-3, 0, -4, 2) | |
| 1041 | - line(3, 0, 4, 2) | |
| 1042 | - pop() | |
| 1038 | + strokeWeight(1); | |
| 1039 | + line(-3, 0, -4, 2); | |
| 1040 | + line(3, 0, 4, 2); | |
| 1041 | + pop(); | |
| 1043 | 1042 | |
| 1044 | - pop() | |
| 1043 | + pop(); | |
| 1045 | 1044 | |
| 1046 | 1045 | } else if (this.type === 'beetle') { |
| 1047 | 1046 | // Big floating beetle! |
| 1048 | - push() | |
| 1049 | - rotate(this.rotation) | |
| 1047 | + push(); | |
| 1048 | + rotate(this.rotation); | |
| 1050 | 1049 | |
| 1051 | 1050 | // Shadow |
| 1052 | - noStroke() | |
| 1053 | - fill(0, 0, 0, 40) | |
| 1054 | - ellipse(3, 3, this.radius * 1.8, this.radius * 2.2) | |
| 1051 | + noStroke(); | |
| 1052 | + fill(0, 0, 0, 40); | |
| 1053 | + ellipse(3, 3, this.radius * 1.8, this.radius * 2.2); | |
| 1055 | 1054 | |
| 1056 | 1055 | // Wings - always visible and flapping since they're floating |
| 1057 | - push() | |
| 1056 | + push(); | |
| 1058 | 1057 | // Wing flap animation |
| 1059 | - let wingAngle = sin(this.wingPhase) * 0.3 | |
| 1060 | - let wingSpread = 15 + sin(this.wingPhase) * 10 | |
| 1058 | + let wingAngle = sin(this.wingPhase) * 0.3; | |
| 1059 | + let wingSpread = 15 + sin(this.wingPhase) * 10; | |
| 1061 | 1060 | |
| 1062 | 1061 | // Left wing |
| 1063 | - push() | |
| 1064 | - translate(-this.radius * 0.4, 0) | |
| 1065 | - rotate(-wingAngle) | |
| 1066 | - fill(255, 255, 255, 120) | |
| 1067 | - stroke(0, 0, 0, 100) | |
| 1068 | - strokeWeight(0.5) | |
| 1069 | - ellipse(-wingSpread * 0.7, 0, wingSpread * 1.2, 15) | |
| 1062 | + push(); | |
| 1063 | + translate(-this.radius * 0.4, 0); | |
| 1064 | + rotate(-wingAngle); | |
| 1065 | + fill(255, 255, 255, 120); | |
| 1066 | + stroke(0, 0, 0, 100); | |
| 1067 | + strokeWeight(0.5); | |
| 1068 | + ellipse(-wingSpread * 0.7, 0, wingSpread * 1.2, 15); | |
| 1070 | 1069 | // Wing details |
| 1071 | - noStroke() | |
| 1072 | - fill(200, 200, 200, 80) | |
| 1073 | - ellipse(-wingSpread * 0.6, 0, wingSpread * 0.8, 10) | |
| 1074 | - pop() | |
| 1070 | + noStroke(); | |
| 1071 | + fill(200, 200, 200, 80); | |
| 1072 | + ellipse(-wingSpread * 0.6, 0, wingSpread * 0.8, 10); | |
| 1073 | + pop(); | |
| 1075 | 1074 | |
| 1076 | 1075 | // Right wing |
| 1077 | - push() | |
| 1078 | - translate(this.radius * 0.4, 0) | |
| 1079 | - rotate(wingAngle) | |
| 1080 | - fill(255, 255, 255, 120) | |
| 1081 | - stroke(0, 0, 0, 100) | |
| 1082 | - strokeWeight(0.5) | |
| 1083 | - ellipse(wingSpread * 0.7, 0, wingSpread * 1.2, 15) | |
| 1076 | + push(); | |
| 1077 | + translate(this.radius * 0.4, 0); | |
| 1078 | + rotate(wingAngle); | |
| 1079 | + fill(255, 255, 255, 120); | |
| 1080 | + stroke(0, 0, 0, 100); | |
| 1081 | + strokeWeight(0.5); | |
| 1082 | + ellipse(wingSpread * 0.7, 0, wingSpread * 1.2, 15); | |
| 1084 | 1083 | // Wing details |
| 1085 | - noStroke() | |
| 1086 | - fill(200, 200, 200, 80) | |
| 1087 | - ellipse(wingSpread * 0.6, 0, wingSpread * 0.8, 10) | |
| 1088 | - pop() | |
| 1084 | + noStroke(); | |
| 1085 | + fill(200, 200, 200, 80); | |
| 1086 | + ellipse(wingSpread * 0.6, 0, wingSpread * 0.8, 10); | |
| 1087 | + pop(); | |
| 1089 | 1088 | |
| 1090 | 1089 | // Extra glow at night |
| 1091 | 1090 | if (gamePhase === 'NIGHT') { |
| 1092 | - noStroke() | |
| 1093 | - fill(255, 255, 200, 30 + sin(this.wingPhase * 2) * 20) | |
| 1094 | - ellipse(0, 0, this.radius * 3, this.radius * 2) | |
| 1091 | + noStroke(); | |
| 1092 | + fill(255, 255, 200, 30 + sin(this.wingPhase * 2) * 20); | |
| 1093 | + ellipse(0, 0, this.radius * 3, this.radius * 2); | |
| 1095 | 1094 | } |
| 1096 | - pop() | |
| 1095 | + pop(); | |
| 1097 | 1096 | |
| 1098 | 1097 | // Main beetle body (on top of wings) |
| 1099 | - fill(red(this.beetleColor), green(this.beetleColor), blue(this.beetleColor)) | |
| 1100 | - stroke(0) | |
| 1101 | - strokeWeight(2) | |
| 1102 | - ellipse(0, 0, this.radius * 1.6, this.radius * 2) | |
| 1098 | + fill(red(this.beetleColor), green(this.beetleColor), blue(this.beetleColor)); | |
| 1099 | + stroke(0); | |
| 1100 | + strokeWeight(2); | |
| 1101 | + ellipse(0, 0, this.radius * 1.6, this.radius * 2); | |
| 1103 | 1102 | |
| 1104 | 1103 | // Shell split line |
| 1105 | - stroke(0) | |
| 1106 | - strokeWeight(1) | |
| 1107 | - line(0, -this.radius, 0, this.radius) | |
| 1104 | + stroke(0); | |
| 1105 | + strokeWeight(1); | |
| 1106 | + line(0, -this.radius, 0, this.radius); | |
| 1108 | 1107 | |
| 1109 | 1108 | // Head |
| 1110 | - fill(10) | |
| 1111 | - ellipse(0, -this.radius * 0.8, this.radius * 0.8, this.radius * 0.6) | |
| 1109 | + fill(10); | |
| 1110 | + ellipse(0, -this.radius * 0.8, this.radius * 0.8, this.radius * 0.6); | |
| 1112 | 1111 | |
| 1113 | 1112 | // Spots/pattern |
| 1114 | - noStroke() | |
| 1115 | - fill(0, 0, 0, 80) | |
| 1116 | - ellipse(-this.radius * 0.3, 0, this.radius * 0.4) | |
| 1117 | - ellipse(this.radius * 0.3, -this.radius * 0.2, this.radius * 0.3) | |
| 1118 | - ellipse(this.radius * 0.2, this.radius * 0.4, this.radius * 0.35) | |
| 1119 | - ellipse(-this.radius * 0.25, this.radius * 0.3, this.radius * 0.25) | |
| 1113 | + noStroke(); | |
| 1114 | + fill(0, 0, 0, 80); | |
| 1115 | + ellipse(-this.radius * 0.3, 0, this.radius * 0.4); | |
| 1116 | + ellipse(this.radius * 0.3, -this.radius * 0.2, this.radius * 0.3); | |
| 1117 | + ellipse(this.radius * 0.2, this.radius * 0.4, this.radius * 0.35); | |
| 1118 | + ellipse(-this.radius * 0.25, this.radius * 0.3, this.radius * 0.25); | |
| 1120 | 1119 | |
| 1121 | 1120 | // No legs - they're flying! |
| 1122 | 1121 | // Just small leg stubs tucked under the body |
| 1123 | - stroke(0) | |
| 1124 | - strokeWeight(1) | |
| 1122 | + stroke(0); | |
| 1123 | + strokeWeight(1); | |
| 1125 | 1124 | // Tiny tucked legs |
| 1126 | - line(-this.radius * 0.5, -this.radius * 0.2, -this.radius * 0.6, -this.radius * 0.1) | |
| 1127 | - line(this.radius * 0.5, -this.radius * 0.2, this.radius * 0.6, -this.radius * 0.1) | |
| 1128 | - line(-this.radius * 0.5, this.radius * 0.2, -this.radius * 0.6, this.radius * 0.1) | |
| 1129 | - line(this.radius * 0.5, this.radius * 0.2, this.radius * 0.6, this.radius * 0.1) | |
| 1125 | + line(-this.radius * 0.5, -this.radius * 0.2, -this.radius * 0.6, -this.radius * 0.1); | |
| 1126 | + line(this.radius * 0.5, -this.radius * 0.2, this.radius * 0.6, -this.radius * 0.1); | |
| 1127 | + line(-this.radius * 0.5, this.radius * 0.2, -this.radius * 0.6, this.radius * 0.1); | |
| 1128 | + line(this.radius * 0.5, this.radius * 0.2, this.radius * 0.6, this.radius * 0.1); | |
| 1130 | 1129 | |
| 1131 | 1130 | // Antennae |
| 1132 | - strokeWeight(1) | |
| 1133 | - line(-3, -this.radius * 1.1, -8, -this.radius * 1.4) | |
| 1134 | - line(3, -this.radius * 1.1, 8, -this.radius * 1.4) | |
| 1131 | + strokeWeight(1); | |
| 1132 | + line(-3, -this.radius * 1.1, -8, -this.radius * 1.4); | |
| 1133 | + line(3, -this.radius * 1.1, 8, -this.radius * 1.4); | |
| 1135 | 1134 | |
| 1136 | 1135 | // Eyes (bigger and more prominent) |
| 1137 | - fill(255, 0, 0) | |
| 1138 | - noStroke() | |
| 1139 | - ellipse(-5, -this.radius * 0.7, 5) | |
| 1140 | - ellipse(5, -this.radius * 0.7, 5) | |
| 1136 | + fill(255, 0, 0); | |
| 1137 | + noStroke(); | |
| 1138 | + ellipse(-5, -this.radius * 0.7, 5); | |
| 1139 | + ellipse(5, -this.radius * 0.7, 5); | |
| 1141 | 1140 | // Eye shine |
| 1142 | - fill(255, 150, 150) | |
| 1143 | - ellipse(-4, -this.radius * 0.72, 2) | |
| 1144 | - ellipse(6, -this.radius * 0.72, 2) | |
| 1141 | + fill(255, 150, 150); | |
| 1142 | + ellipse(-4, -this.radius * 0.72, 2); | |
| 1143 | + ellipse(6, -this.radius * 0.72, 2); | |
| 1145 | 1144 | |
| 1146 | - pop() | |
| 1145 | + pop(); | |
| 1147 | 1146 | |
| 1148 | 1147 | } else if (this.type === 'leaf') { |
| 1149 | 1148 | // Original leaf code |
| 1150 | - rotate(this.rotation) | |
| 1149 | + rotate(this.rotation); | |
| 1151 | 1150 | |
| 1152 | 1151 | if (gamePhase === 'NIGHT') { |
| 1153 | - fill(20, 40, 20) | |
| 1154 | - stroke(10, 20, 10) | |
| 1152 | + fill(20, 40, 20); | |
| 1153 | + stroke(10, 20, 10); | |
| 1155 | 1154 | } else { |
| 1156 | - fill(34, 139, 34) | |
| 1157 | - stroke(25, 100, 25) | |
| 1155 | + fill(34, 139, 34); | |
| 1156 | + stroke(25, 100, 25); | |
| 1158 | 1157 | } |
| 1159 | - strokeWeight(2) | |
| 1158 | + strokeWeight(2); | |
| 1160 | 1159 | |
| 1161 | - beginShape() | |
| 1160 | + beginShape(); | |
| 1162 | 1161 | for (let point of this.leafPoints) { |
| 1163 | - let x = cos(point.angle) * point.radius | |
| 1164 | - let y = sin(point.angle) * point.radius | |
| 1165 | - curveVertex(x, y) | |
| 1162 | + let x = cos(point.angle) * point.radius; | |
| 1163 | + let y = sin(point.angle) * point.radius; | |
| 1164 | + curveVertex(x, y); | |
| 1166 | 1165 | } |
| 1167 | - let firstPoint = this.leafPoints[0] | |
| 1166 | + let firstPoint = this.leafPoints[0]; | |
| 1168 | 1167 | curveVertex( |
| 1169 | 1168 | cos(firstPoint.angle) * firstPoint.radius, |
| 1170 | 1169 | sin(firstPoint.angle) * firstPoint.radius |
| 1171 | - ) | |
| 1172 | - let secondPoint = this.leafPoints[1] | |
| 1170 | + ); | |
| 1171 | + let secondPoint = this.leafPoints[1]; | |
| 1173 | 1172 | curveVertex( |
| 1174 | 1173 | cos(secondPoint.angle) * secondPoint.radius, |
| 1175 | 1174 | sin(secondPoint.angle) * secondPoint.radius |
| 1176 | - ) | |
| 1177 | - endShape() | |
| 1178 | - | |
| 1179 | - stroke(25, 100, 25, 100) | |
| 1180 | - strokeWeight(1) | |
| 1181 | - line(0, -this.radius, 0, this.radius) | |
| 1182 | - line(0, 0, -this.radius / 2, -this.radius / 2) | |
| 1183 | - line(0, 0, this.radius / 2, -this.radius / 2) | |
| 1184 | - line(0, 0, -this.radius / 2, this.radius / 2) | |
| 1185 | - line(0, 0, this.radius / 2, this.radius / 2) | |
| 1175 | + ); | |
| 1176 | + endShape(); | |
| 1177 | + | |
| 1178 | + stroke(25, 100, 25, 100); | |
| 1179 | + strokeWeight(1); | |
| 1180 | + line(0, -this.radius, 0, this.radius); | |
| 1181 | + line(0, 0, -this.radius / 2, -this.radius / 2); | |
| 1182 | + line(0, 0, this.radius / 2, -this.radius / 2); | |
| 1183 | + line(0, 0, -this.radius / 2, this.radius / 2); | |
| 1184 | + line(0, 0, this.radius / 2, this.radius / 2); | |
| 1186 | 1185 | |
| 1187 | 1186 | } else if (this.type === 'branch') { |
| 1188 | 1187 | // Keep old branch code for backwards compatibility |
| 1189 | - rotate(this.rotation) | |
| 1188 | + rotate(this.rotation); | |
| 1190 | 1189 | |
| 1191 | 1190 | if (gamePhase === 'NIGHT') { |
| 1192 | - stroke(40, 20, 0) | |
| 1193 | - fill(50, 25, 5) | |
| 1191 | + stroke(40, 20, 0); | |
| 1192 | + fill(50, 25, 5); | |
| 1194 | 1193 | } else { |
| 1195 | - stroke(101, 67, 33) | |
| 1196 | - fill(139, 90, 43) | |
| 1194 | + stroke(101, 67, 33); | |
| 1195 | + fill(139, 90, 43); | |
| 1197 | 1196 | } |
| 1198 | - strokeWeight(3) | |
| 1197 | + strokeWeight(3); | |
| 1199 | 1198 | |
| 1200 | - push() | |
| 1201 | - strokeWeight(this.radius / 3) | |
| 1202 | - line(-this.radius, 0, this.radius, 0) | |
| 1199 | + push(); | |
| 1200 | + strokeWeight(this.radius / 3); | |
| 1201 | + line(-this.radius, 0, this.radius, 0); | |
| 1203 | 1202 | |
| 1204 | - strokeWeight(2) | |
| 1205 | - line(-this.radius / 2, 0, -this.radius / 2 - 10, -10) | |
| 1206 | - line(this.radius / 3, 0, this.radius / 3 + 8, -8) | |
| 1207 | - line(0, 0, 5, -15) | |
| 1203 | + strokeWeight(2); | |
| 1204 | + line(-this.radius / 2, 0, -this.radius / 2 - 10, -10); | |
| 1205 | + line(this.radius / 3, 0, this.radius / 3 + 8, -8); | |
| 1206 | + line(0, 0, 5, -15); | |
| 1208 | 1207 | |
| 1209 | - stroke(80, 50, 20, 100) | |
| 1210 | - strokeWeight(1) | |
| 1208 | + stroke(80, 50, 20, 100); | |
| 1209 | + strokeWeight(1); | |
| 1211 | 1210 | for (let i = -this.radius; i < this.radius; i += 5) { |
| 1212 | - line(i, -2, i + 2, 2) | |
| 1211 | + line(i, -2, i + 2, 2); | |
| 1213 | 1212 | } |
| 1214 | - pop() | |
| 1213 | + pop(); | |
| 1215 | 1214 | |
| 1216 | - noStroke() | |
| 1217 | - fill(255, 255, 255, 30) | |
| 1218 | - ellipse(0, 0, this.radius * 2) | |
| 1215 | + noStroke(); | |
| 1216 | + fill(255, 255, 255, 30); | |
| 1217 | + ellipse(0, 0, this.radius * 2); | |
| 1219 | 1218 | } |
| 1220 | 1219 | |
| 1221 | - pop() | |
| 1220 | + pop(); | |
| 1222 | 1221 | } |
| 1223 | 1222 | } |
| 1224 | 1223 | |
| 1225 | 1224 | class FoodBox { |
| 1226 | - constructor (x, y) { | |
| 1227 | - this.pos = createVector(x, y) | |
| 1228 | - this.radius = 10 | |
| 1229 | - this.collected = false | |
| 1230 | - this.floatOffset = random(TWO_PI) | |
| 1231 | - this.silkValue = random(20, 35) | |
| 1232 | - this.glowPhase = random(TWO_PI) | |
| 1225 | + constructor(x, y) { | |
| 1226 | + this.pos = createVector(x, y); | |
| 1227 | + this.radius = 10; | |
| 1228 | + this.collected = false; | |
| 1229 | + this.floatOffset = random(TWO_PI); | |
| 1230 | + this.silkValue = random(20, 35); | |
| 1231 | + this.glowPhase = random(TWO_PI); | |
| 1233 | 1232 | } |
| 1234 | 1233 | |
| 1235 | - collect () { | |
| 1236 | - webSilk = min(webSilk + this.silkValue, maxWebSilk) | |
| 1234 | + collect() { | |
| 1235 | + webSilk = min(webSilk + this.silkValue, maxWebSilk); | |
| 1237 | 1236 | |
| 1238 | 1237 | for (let i = 0; i < 8; i++) { |
| 1239 | - particles.push(new Particle(this.pos.x, this.pos.y)) | |
| 1238 | + particles.push(new Particle(this.pos.x, this.pos.y)); | |
| 1240 | 1239 | } |
| 1241 | 1240 | } |
| 1242 | 1241 | |
| 1243 | - display () { | |
| 1244 | - push() | |
| 1245 | - let floatY = sin(frameCount * 0.05 + this.floatOffset) * 3 | |
| 1246 | - translate(this.pos.x, this.pos.y + floatY) | |
| 1247 | - | |
| 1248 | - let glowIntensity = 100 + sin(frameCount * 0.1 + this.glowPhase) * 50 | |
| 1249 | - noStroke() | |
| 1250 | - fill(255, 200, 100, glowIntensity * 0.3) | |
| 1251 | - ellipse(0, 0, 40) | |
| 1252 | - fill(255, 220, 150, glowIntensity * 0.5) | |
| 1253 | - ellipse(0, 0, 25) | |
| 1254 | - | |
| 1255 | - rectMode(CENTER) | |
| 1256 | - | |
| 1257 | - fill(0, 0, 0, 50) | |
| 1258 | - rect(2, 2, this.radius * 2, this.radius * 1.8, 3) | |
| 1259 | - | |
| 1260 | - fill(139, 69, 19) | |
| 1261 | - stroke(100, 50, 0) | |
| 1262 | - strokeWeight(1) | |
| 1263 | - rect(0, 0, this.radius * 2, this.radius * 1.8, 3) | |
| 1264 | - | |
| 1265 | - stroke(100, 50, 0) | |
| 1266 | - strokeWeight(1) | |
| 1267 | - line(-this.radius, 0, this.radius, 0) | |
| 1268 | - line(0, -this.radius * 0.9, 0, this.radius * 0.9) | |
| 1269 | - | |
| 1270 | - noStroke() | |
| 1271 | - fill(255, 200, 100) | |
| 1272 | - ellipse(-5, -4, 4) | |
| 1273 | - ellipse(5, -4, 3) | |
| 1274 | - ellipse(-4, 5, 3) | |
| 1275 | - ellipse(4, 4, 4) | |
| 1276 | - | |
| 1277 | - pop() | |
| 1242 | + display() { | |
| 1243 | + push(); | |
| 1244 | + let floatY = sin(frameCount * 0.05 + this.floatOffset) * 3; | |
| 1245 | + translate(this.pos.x, this.pos.y + floatY); | |
| 1246 | + | |
| 1247 | + let glowIntensity = 100 + sin(frameCount * 0.1 + this.glowPhase) * 50; | |
| 1248 | + noStroke(); | |
| 1249 | + fill(255, 200, 100, glowIntensity * 0.3); | |
| 1250 | + ellipse(0, 0, 40); | |
| 1251 | + fill(255, 220, 150, glowIntensity * 0.5); | |
| 1252 | + ellipse(0, 0, 25); | |
| 1253 | + | |
| 1254 | + rectMode(CENTER); | |
| 1255 | + | |
| 1256 | + fill(0, 0, 0, 50); | |
| 1257 | + rect(2, 2, this.radius * 2, this.radius * 1.8, 3); | |
| 1258 | + | |
| 1259 | + fill(139, 69, 19); | |
| 1260 | + stroke(100, 50, 0); | |
| 1261 | + strokeWeight(1); | |
| 1262 | + rect(0, 0, this.radius * 2, this.radius * 1.8, 3); | |
| 1263 | + | |
| 1264 | + stroke(100, 50, 0); | |
| 1265 | + strokeWeight(1); | |
| 1266 | + line(-this.radius, 0, this.radius, 0); | |
| 1267 | + line(0, -this.radius * 0.9, 0, this.radius * 0.9); | |
| 1268 | + | |
| 1269 | + noStroke(); | |
| 1270 | + fill(255, 200, 100); | |
| 1271 | + ellipse(-5, -4, 4); | |
| 1272 | + ellipse(5, -4, 3); | |
| 1273 | + ellipse(-4, 5, 3); | |
| 1274 | + ellipse(4, 4, 4); | |
| 1275 | + | |
| 1276 | + pop(); | |
| 1278 | 1277 | } |
| 1279 | 1278 | } |
| 1280 | 1279 | |
| 1281 | 1280 | class Particle { |
| 1282 | - constructor (x, y) { | |
| 1283 | - this.pos = createVector(x, y) | |
| 1284 | - this.vel = createVector(random(-3, 3), random(-5, -2)) | |
| 1285 | - this.lifetime = 255 | |
| 1286 | - this.color = color(255, random(200, 255), random(100, 200)) | |
| 1287 | - this.size = 6 // Default size | |
| 1281 | + constructor(x, y) { | |
| 1282 | + this.pos = createVector(x, y); | |
| 1283 | + this.vel = createVector(random(-3, 3), random(-5, -2)); | |
| 1284 | + this.lifetime = 255; | |
| 1285 | + this.color = color(255, random(200, 255), random(100, 200)); | |
| 1286 | + this.size = 6; // Default size | |
| 1288 | 1287 | } |
| 1289 | 1288 | |
| 1290 | - update () { | |
| 1291 | - this.vel.y += 0.2 | |
| 1292 | - this.pos.add(this.vel) | |
| 1293 | - this.lifetime -= 8 | |
| 1289 | + update() { | |
| 1290 | + this.vel.y += 0.2; | |
| 1291 | + this.pos.add(this.vel); | |
| 1292 | + this.lifetime -= 8; | |
| 1294 | 1293 | } |
| 1295 | 1294 | |
| 1296 | - display () { | |
| 1297 | - push() | |
| 1298 | - noStroke() | |
| 1299 | - fill(red(this.color), green(this.color), blue(this.color), this.lifetime) | |
| 1300 | - ellipse(this.pos.x, this.pos.y, this.size) | |
| 1301 | - pop() | |
| 1295 | + display() { | |
| 1296 | + push(); | |
| 1297 | + noStroke(); | |
| 1298 | + fill(red(this.color), green(this.color), blue(this.color), this.lifetime); | |
| 1299 | + ellipse(this.pos.x, this.pos.y, this.size); | |
| 1300 | + pop(); | |
| 1302 | 1301 | } |
| 1303 | 1302 | |
| 1304 | - isDead () { | |
| 1305 | - return this.lifetime <= 0 | |
| 1303 | + isDead() { | |
| 1304 | + return this.lifetime <= 0; | |
| 1306 | 1305 | } |
| 1307 | 1306 | } |
js/game.jsmodified@@ -21,21 +21,240 @@ let maxWebSilk = 100; | ||
| 21 | 21 | let silkRechargeRate = 0.05; |
| 22 | 22 | let silkDrainRate = 2; |
| 23 | 23 | |
| 24 | -// Game phases | |
| 24 | +// Game phases - PHASE 1 UPDATES | |
| 25 | 25 | let gamePhase = 'DUSK'; |
| 26 | 26 | let phaseTimer = 0; |
| 27 | -let DUSK_DURATION = 1500; // 25 seconds | |
| 27 | + | |
| 28 | +// Phase durations (in frames, 60fps) - PHASE 1 NEW | |
| 29 | +let DAWN_DURATION = 1800; // 30 seconds | |
| 30 | +let DAY_DURATION = 2700; // 45 seconds | |
| 31 | +let DUSK_DURATION = 1800; // 30 seconds (was 1500) | |
| 32 | +let NIGHT_DURATION = 3600; // 60 seconds | |
| 28 | 33 | let TRANSITION_DURATION = 180; // 3 seconds |
| 34 | + | |
| 29 | 35 | let skyColor1, skyColor2, currentSkyColor1, currentSkyColor2; |
| 30 | 36 | let moonY = 100; |
| 31 | 37 | let moonOpacity = 0; |
| 38 | +let sunY = -50; // PHASE 1 NEW | |
| 39 | +let sunOpacity = 0; // PHASE 1 NEW | |
| 40 | + | |
| 41 | +// Progression tracking - PHASE 1 NEW | |
| 32 | 42 | let fliesCaught = 0; |
| 33 | 43 | let fliesMunched = 0; |
| 44 | +let totalFliesCaught = 0; // Lifetime counter | |
| 45 | +let nightsSurvived = 0; | |
| 46 | +let currentNight = 1; | |
| 47 | +let baseFlySpeed = 3; | |
| 48 | +let fliesEscaped = []; | |
| 49 | + | |
| 50 | +// PHASE 2: Special fly notifications | |
| 51 | +let notifications = []; | |
| 52 | + | |
| 53 | +// PHASE 3: Upgrade System | |
| 54 | +let playerPoints = 0; | |
| 55 | +let shopOpen = false; | |
| 56 | + | |
| 57 | +// PHASE 4: Dawn Exhaustion System | |
| 58 | +let jumpStamina = 100; | |
| 59 | +let maxJumpStamina = 100; | |
| 60 | +let jumpCost = 20; | |
| 61 | +let staminaRegenRate = 0.2; | |
| 62 | +let isExhausted = false; | |
| 63 | +let fliesMunchedLastNight = 0; | |
| 64 | +let birds = []; | |
| 65 | + | |
| 66 | +// PHASE 4B: Wind System | |
| 67 | +let windActive = false; | |
| 68 | +let windDirection = 0; | |
| 69 | +let windStrength = 0; | |
| 70 | +let windTimer = 0; | |
| 71 | +let windDuration = 0; | |
| 72 | +let windParticles = []; | |
| 73 | +let nextWindTime = 0; | |
| 74 | + | |
| 75 | +// PHASE 4B: Thief bird timer | |
| 76 | +let thiefBirdTimer = 0; | |
| 77 | +let nextThiefTime = 0; | |
| 78 | + | |
| 79 | +// PHASE 5: Achievements & Stats System | |
| 80 | +let achievements = { | |
| 81 | + nightOwl: { name: "Night Owl", desc: "Survive 10 nights", icon: "🦉", unlocked: false, progress: 0, target: 10 }, | |
| 82 | + silkMaster: { name: "Silk Master", desc: "Have 15+ strands at once", icon: "🕸️", unlocked: false, progress: 0, target: 15 }, | |
| 83 | + feast: { name: "Feast", desc: "Munch 20 flies in one night", icon: "🍽️", unlocked: false, progress: 0, target: 20 }, | |
| 84 | + architect: { name: "Architect", desc: "Catch 5 flies without munching", icon: "🏗️", unlocked: false, progress: 0, target: 5 }, | |
| 85 | + untouchable: { name: "Untouchable", desc: "Survive a night without losing a strand", icon: "💎", unlocked: false }, | |
| 86 | + windRider: { name: "Wind Rider", desc: "Jump 10 times during wind", icon: "🌬️", unlocked: false, progress: 0, target: 10 }, | |
| 87 | + thiefDefender: { name: "Thief Defender", desc: "Scare off 10 thief birds", icon: "🛡️", unlocked: false, progress: 0, target: 10 }, | |
| 88 | + exhaustionMaster: { name: "Exhaustion Master", desc: "Survive dawn with < 20 stamina", icon: "😴", unlocked: false }, | |
| 89 | + queenSlayer: { name: "Queen Slayer", desc: "Catch 10 queen flies", icon: "👑", unlocked: false, progress: 0, target: 10 }, | |
| 90 | + perfectDawn: { name: "Perfect Dawn", desc: "No bird hits during dawn", icon: "☀️", unlocked: false }, | |
| 91 | + speedrunner: { name: "Speedrunner", desc: "Catch 30 flies before Night 5", icon: "⚡", unlocked: false }, | |
| 92 | + galaxyUnlock: { name: "Cosmic Spider", desc: "Survive 15 nights", icon: "🌌", unlocked: false, progress: 0, target: 15 }, | |
| 93 | + goldenHunter: { name: "Golden Hunter", desc: "Catch 100 golden flies", icon: "✨", unlocked: false, progress: 0, target: 100 }, | |
| 94 | + shadowPredator: { name: "Shadow Predator", desc: "Catch 50 flies in one night", icon: "🌑", unlocked: false, progress: 0, target: 50 }, | |
| 95 | + webMaster: { name: "Web Master", desc: "500 total flies caught", icon: "🏆", unlocked: false, progress: 0, target: 500 } | |
| 96 | +}; | |
| 97 | + | |
| 98 | +// Statistics tracking | |
| 99 | +let stats = { | |
| 100 | + totalFliesCaught: 0, | |
| 101 | + regularCaught: 0, | |
| 102 | + goldenCaught: 0, | |
| 103 | + mothsCaught: 0, | |
| 104 | + queensCaught: 0, | |
| 105 | + longestNight: 0, | |
| 106 | + totalSilkSpun: 0, | |
| 107 | + totalJumps: 0, | |
| 108 | + windJumps: 0, | |
| 109 | + thievesScared: 0, | |
| 110 | + birdHitsTaken: 0, | |
| 111 | + strandsCreated: 0, | |
| 112 | + perfectDawns: 0, | |
| 113 | + fliesMunchedInCurrentNight: 0, | |
| 114 | + fliesCaughtWithoutMunch: 0, | |
| 115 | + strandsLostInNight: 0 | |
| 116 | +}; | |
| 117 | + | |
| 118 | +// Cosmetics | |
| 119 | +let unlockedSkins = { | |
| 120 | + default: true, | |
| 121 | + galaxy: false, | |
| 122 | + golden: false, | |
| 123 | + shadow: false, | |
| 124 | + rainbow: false | |
| 125 | +}; | |
| 126 | + | |
| 127 | +let currentSkin = 'default'; | |
| 128 | +let achievementQueue = []; | |
| 129 | +let showingAchievement = null; | |
| 130 | +let achievementDisplayTimer = 0; | |
| 131 | +let upgrades = { | |
| 132 | + // Tier 1 Upgrades | |
| 133 | + strongLegs: { | |
| 134 | + level: 0, | |
| 135 | + maxLevel: 3, | |
| 136 | + cost: 15, | |
| 137 | + name: "Strong Legs", | |
| 138 | + description: "Jump 15% farther", | |
| 139 | + icon: "🦵", | |
| 140 | + tier: 1 | |
| 141 | + }, | |
| 142 | + silkGlands: { | |
| 143 | + level: 0, | |
| 144 | + maxLevel: 3, | |
| 145 | + cost: 20, | |
| 146 | + name: "Silk Glands", | |
| 147 | + description: "+20 max silk capacity", | |
| 148 | + icon: "🕸️", | |
| 149 | + tier: 1 | |
| 150 | + }, | |
| 151 | + efficientSpinning: { | |
| 152 | + level: 0, | |
| 153 | + maxLevel: 3, | |
| 154 | + cost: 15, | |
| 155 | + name: "Efficient Spinning", | |
| 156 | + description: "-20% silk consumption", | |
| 157 | + icon: "♻️", | |
| 158 | + tier: 1 | |
| 159 | + }, | |
| 160 | + quickMunch: { | |
| 161 | + level: 0, | |
| 162 | + maxLevel: 2, | |
| 163 | + cost: 10, | |
| 164 | + name: "Quick Munch", | |
| 165 | + description: "Munch cooldown -30%", | |
| 166 | + icon: "🦷", | |
| 167 | + tier: 1 | |
| 168 | + }, | |
| 169 | + | |
| 170 | + // Tier 2 Upgrades (requires at least 2 Tier 1 upgrades) | |
| 171 | + powerJump: { | |
| 172 | + level: 0, | |
| 173 | + maxLevel: 1, | |
| 174 | + cost: 50, | |
| 175 | + name: "Power Jump", | |
| 176 | + description: "Hold to charge jump (2x distance)", | |
| 177 | + icon: "⚡", | |
| 178 | + tier: 2, | |
| 179 | + requires: 2 // Number of tier 1 upgrades needed | |
| 180 | + }, | |
| 181 | + silkRecycle: { | |
| 182 | + level: 0, | |
| 183 | + maxLevel: 1, | |
| 184 | + cost: 75, | |
| 185 | + name: "Silk Recycle", | |
| 186 | + description: "Press R near old web to recover 50% silk", | |
| 187 | + icon: "🔄", | |
| 188 | + tier: 2, | |
| 189 | + requires: 2 | |
| 190 | + }, | |
| 191 | + spiderSense: { | |
| 192 | + level: 0, | |
| 193 | + maxLevel: 1, | |
| 194 | + cost: 100, | |
| 195 | + name: "Spider Sense", | |
| 196 | + description: "See faint prediction lines for fly paths", | |
| 197 | + icon: "👁️", | |
| 198 | + tier: 2, | |
| 199 | + requires: 3 | |
| 200 | + }, | |
| 201 | + metabolize: { | |
| 202 | + level: 0, | |
| 203 | + maxLevel: 1, | |
| 204 | + cost: 60, | |
| 205 | + name: "Metabolize", | |
| 206 | + description: "Munching heals nearby broken strands", | |
| 207 | + icon: "💚", | |
| 208 | + tier: 2, | |
| 209 | + requires: 2 | |
| 210 | + } | |
| 211 | +}; | |
| 212 | + | |
| 213 | +// Track if charging jump (Tier 2 upgrade) | |
| 214 | +let chargingJump = false; | |
| 215 | +let jumpChargeTime = 0; | |
| 216 | +let maxJumpCharge = 60; // 1 second at 60fps | |
| 217 | + | |
| 218 | +class Notification { | |
| 219 | + constructor(text, color) { | |
| 220 | + this.text = text; | |
| 221 | + this.color = color; | |
| 222 | + this.y = height * 0.3; | |
| 223 | + this.alpha = 255; | |
| 224 | + this.lifetime = 180; // 3 seconds | |
| 225 | + } | |
| 226 | + | |
| 227 | + update() { | |
| 228 | + this.lifetime--; | |
| 229 | + if (this.lifetime < 60) { | |
| 230 | + this.alpha = map(this.lifetime, 0, 60, 0, 255); | |
| 231 | + } | |
| 232 | + this.y -= 0.5; // Slowly rise | |
| 233 | + } | |
| 234 | + | |
| 235 | + display() { | |
| 236 | + push(); | |
| 237 | + textAlign(CENTER); | |
| 238 | + textSize(24); | |
| 239 | + strokeWeight(4); | |
| 240 | + stroke(0, 0, 0, this.alpha); | |
| 241 | + fill(red(this.color), green(this.color), blue(this.color), this.alpha); | |
| 242 | + text(this.text, width / 2, this.y); | |
| 243 | + pop(); | |
| 244 | + } | |
| 245 | + | |
| 246 | + isDead() { | |
| 247 | + return this.lifetime <= 0; | |
| 248 | + } | |
| 249 | +} | |
| 34 | 250 | |
| 35 | 251 | function setup() { |
| 36 | 252 | let canvas = createCanvas(window.innerWidth, window.innerHeight); |
| 37 | 253 | canvas.parent('game-container'); |
| 38 | 254 | |
| 255 | + // PHASE 5: Load saved game | |
| 256 | + loadGame(); | |
| 257 | + | |
| 39 | 258 | skyColor1 = color(135, 206, 235); |
| 40 | 259 | skyColor2 = color(255, 183, 77); |
| 41 | 260 | currentSkyColor1 = skyColor1; |
@@ -106,6 +325,9 @@ function setup() { | ||
| 106 | 325 | // Place spider on top of the visual branch at the tip (8 is spider radius) |
| 107 | 326 | spider = new Spider(spiderStartX, branchSurfaceY - 8); |
| 108 | 327 | |
| 328 | + // PHASE 3: Apply any existing upgrades at start | |
| 329 | + applyUpgradeEffects(); | |
| 330 | + | |
| 109 | 331 | // Add invisible obstacles along the branch for web anchor points |
| 110 | 332 | let numBranchAnchors = 3; |
| 111 | 333 | for (let i = 0; i < numBranchAnchors; i++) { |
@@ -270,19 +492,65 @@ function draw() { | ||
| 270 | 492 | // Update phase timer |
| 271 | 493 | phaseTimer++; |
| 272 | 494 | |
| 273 | - // Phase transitions | |
| 495 | + // Phase transitions with endless cycle - PHASE 1 UPDATE | |
| 274 | 496 | if (gamePhase === 'DUSK' && phaseTimer >= DUSK_DURATION) { |
| 275 | - gamePhase = 'TRANSITION'; | |
| 497 | + gamePhase = 'DUSK_TO_NIGHT'; | |
| 276 | 498 | phaseTimer = 0; |
| 277 | - } else if (gamePhase === 'TRANSITION' && phaseTimer >= TRANSITION_DURATION) { | |
| 499 | + } else if (gamePhase === 'DUSK_TO_NIGHT' && phaseTimer >= TRANSITION_DURATION) { | |
| 278 | 500 | gamePhase = 'NIGHT'; |
| 279 | 501 | phaseTimer = 0; |
| 280 | - for (let i = 0; i < 5; i++) { | |
| 281 | - flies.push(new Fly()); | |
| 282 | - } | |
| 283 | - for (let i = 0; i < 3; i++) { | |
| 284 | - spawnFoodBox(); | |
| 502 | + // Spawn flies based on difficulty | |
| 503 | + spawnNightFlies(); | |
| 504 | + } else if (gamePhase === 'NIGHT' && phaseTimer >= NIGHT_DURATION) { | |
| 505 | + gamePhase = 'NIGHT_TO_DAWN'; | |
| 506 | + phaseTimer = 0; | |
| 507 | + nightsSurvived++; | |
| 508 | + currentNight++; | |
| 509 | + // PHASE 5: Check night achievements | |
| 510 | + checkNightAchievements(); | |
| 511 | + // PHASE 4: Track flies munched for dawn stamina | |
| 512 | + fliesMunchedLastNight = fliesMunched; | |
| 513 | + fliesMunched = 0; // Reset for next night | |
| 514 | + // PHASE 4B: Clear any thief birds | |
| 515 | + birds = birds.filter(b => !b.isThief); | |
| 516 | + windActive = false; // Stop any active wind | |
| 517 | + } else if (gamePhase === 'NIGHT_TO_DAWN' && phaseTimer >= TRANSITION_DURATION) { | |
| 518 | + gamePhase = 'DAWN'; | |
| 519 | + phaseTimer = 0; | |
| 520 | + // PHASE 4: Calculate dawn stamina and spawn birds | |
| 521 | + maxJumpStamina = 30 + (fliesMunchedLastNight * 10); | |
| 522 | + maxJumpStamina = min(maxJumpStamina, 150); // Cap at 150 | |
| 523 | + jumpStamina = maxJumpStamina; | |
| 524 | + // Spawn birds | |
| 525 | + spawnDawnBirds(); | |
| 526 | + // Flies escape at dawn | |
| 527 | + escapeFlies(); | |
| 528 | + } else if (gamePhase === 'DAWN' && phaseTimer >= DAWN_DURATION) { | |
| 529 | + gamePhase = 'DAWN_TO_DAY'; | |
| 530 | + phaseTimer = 0; | |
| 531 | + // PHASE 5: Check dawn achievements | |
| 532 | + checkDawnAchievements(); | |
| 533 | + // PHASE 4: Clear birds when dawn ends | |
| 534 | + birds = []; | |
| 535 | + // PHASE 3: Open shop at dawn | |
| 536 | + if (currentNight > 1) { | |
| 537 | + openUpgradeShop(); | |
| 285 | 538 | } |
| 539 | + } else if (gamePhase === 'DAWN_TO_DAY' && phaseTimer >= TRANSITION_DURATION) { | |
| 540 | + gamePhase = 'DAY'; | |
| 541 | + phaseTimer = 0; | |
| 542 | + // Degrade webs by 10% | |
| 543 | + degradeWebs(); | |
| 544 | + // PHASE 5: Open stats panel during day | |
| 545 | + openStatsPanel(); | |
| 546 | + } else if (gamePhase === 'DAY' && phaseTimer >= DAY_DURATION) { | |
| 547 | + gamePhase = 'DAY_TO_DUSK'; | |
| 548 | + phaseTimer = 0; | |
| 549 | + } else if (gamePhase === 'DAY_TO_DUSK' && phaseTimer >= TRANSITION_DURATION) { | |
| 550 | + gamePhase = 'DUSK'; | |
| 551 | + phaseTimer = 0; | |
| 552 | + // Return some flies for next night | |
| 553 | + prepareDusk(); | |
| 286 | 554 | } |
| 287 | 555 | |
| 288 | 556 | // Update sky colors |
@@ -296,6 +564,63 @@ function draw() { | ||
| 296 | 564 | drawMoon(); |
| 297 | 565 | } |
| 298 | 566 | |
| 567 | + // Draw sun during day phases - PHASE 1 NEW | |
| 568 | + if (sunOpacity > 0) { | |
| 569 | + drawSun(); | |
| 570 | + } | |
| 571 | + | |
| 572 | + // PHASE 4B: Update wind system | |
| 573 | + updateWind(); | |
| 574 | + | |
| 575 | + // PHASE 4B: Apply wind to airborne entities | |
| 576 | + if (windActive) { | |
| 577 | + // Push spider if airborne | |
| 578 | + if (spider.isAirborne) { | |
| 579 | + spider.vel.x += cos(windDirection) * windStrength * 0.1; | |
| 580 | + } | |
| 581 | + | |
| 582 | + // Push flies | |
| 583 | + for (let fly of flies) { | |
| 584 | + if (!fly.stuck && !fly.caught) { | |
| 585 | + fly.vel.x += cos(windDirection) * windStrength * 0.05; | |
| 586 | + } | |
| 587 | + } | |
| 588 | + | |
| 589 | + // Make webs sway | |
| 590 | + for (let strand of webStrands) { | |
| 591 | + if (!strand.broken) { | |
| 592 | + strand.vibrate(windStrength * 0.5); | |
| 593 | + // Check if strand is overstretched and should break | |
| 594 | + if (strand.tension > 1.2 && windStrength > 3) { | |
| 595 | + if (random() < 0.01) { // Small chance per frame | |
| 596 | + strand.broken = true; | |
| 597 | + notifications.push(new Notification("Wind snapped a web!", color(255, 150, 100))); | |
| 598 | + } | |
| 599 | + } | |
| 600 | + } | |
| 601 | + } | |
| 602 | + | |
| 603 | + // Update wind particles | |
| 604 | + for (let i = windParticles.length - 1; i >= 0; i--) { | |
| 605 | + let p = windParticles[i]; | |
| 606 | + p.x += cos(windDirection) * windStrength * 3; | |
| 607 | + p.life--; | |
| 608 | + if (p.life <= 0 || p.x < -50 || p.x > width + 50) { | |
| 609 | + windParticles.splice(i, 1); | |
| 610 | + } | |
| 611 | + } | |
| 612 | + | |
| 613 | + // Spawn new wind particles | |
| 614 | + if (frameCount % 5 === 0) { | |
| 615 | + windParticles.push({ | |
| 616 | + x: windDirection > 0 ? -20 : width + 20, | |
| 617 | + y: random(height), | |
| 618 | + life: 120, | |
| 619 | + size: random(2, 4) | |
| 620 | + }); | |
| 621 | + } | |
| 622 | + } | |
| 623 | + | |
| 299 | 624 | // Update and display game objects |
| 300 | 625 | for (let obstacle of obstacles) { |
| 301 | 626 | obstacle.update(); // Update movement and animations |
@@ -306,6 +631,40 @@ function draw() { | ||
| 306 | 631 | box.display(); |
| 307 | 632 | } |
| 308 | 633 | |
| 634 | + // PHASE 4B: Display wind effects | |
| 635 | + if (windActive) { | |
| 636 | + push(); | |
| 637 | + noStroke(); | |
| 638 | + for (let p of windParticles) { | |
| 639 | + fill(255, 255, 255, p.life * 0.5); | |
| 640 | + ellipse(p.x, p.y, p.size); | |
| 641 | + } | |
| 642 | + | |
| 643 | + // Wind indicator | |
| 644 | + push(); | |
| 645 | + translate(width / 2, 50); | |
| 646 | + stroke(255, 255, 255, 100); | |
| 647 | + strokeWeight(3); | |
| 648 | + let arrowLength = windStrength * 10; | |
| 649 | + line(0, 0, cos(windDirection) * arrowLength, 0); | |
| 650 | + // Arrowhead | |
| 651 | + push(); | |
| 652 | + translate(cos(windDirection) * arrowLength, 0); | |
| 653 | + rotate(windDirection); | |
| 654 | + line(0, 0, -5, -3); | |
| 655 | + line(0, 0, -5, 3); | |
| 656 | + pop(); | |
| 657 | + | |
| 658 | + // Wind strength text | |
| 659 | + fill(255, 255, 255, 150); | |
| 660 | + noStroke(); | |
| 661 | + textAlign(CENTER); | |
| 662 | + textSize(12); | |
| 663 | + text("WIND: " + Math.round(windStrength), 0, 20); | |
| 664 | + pop(); | |
| 665 | + pop(); | |
| 666 | + } | |
| 667 | + | |
| 309 | 668 | for (let i = particles.length - 1; i >= 0; i--) { |
| 310 | 669 | particles[i].update(); |
| 311 | 670 | particles[i].display(); |
@@ -314,6 +673,7 @@ function draw() { | ||
| 314 | 673 | } |
| 315 | 674 | } |
| 316 | 675 | |
| 676 | + // PHASE 1 UPDATE - Handle broken strands | |
| 317 | 677 | for (let i = webStrands.length - 1; i >= 0; i--) { |
| 318 | 678 | let strand = webStrands[i]; |
| 319 | 679 | strand.update(); |
@@ -363,7 +723,7 @@ function draw() { | ||
| 363 | 723 | fly.slowedBy.clear(); |
| 364 | 724 | fly.vel = createVector(random(-0.5, 0.5), 1.5); |
| 365 | 725 | |
| 366 | - // Yellow particles for release | |
| 726 | + // Create release particles | |
| 367 | 727 | for (let j = 0; j < 3; j++) { |
| 368 | 728 | let p = new Particle(fly.pos.x, fly.pos.y); |
| 369 | 729 | p.color = color(255, 255, 0, 100); |
@@ -414,40 +774,894 @@ function draw() { | ||
| 414 | 774 | spider.update(); |
| 415 | 775 | spider.display(); |
| 416 | 776 | |
| 777 | + // PHASE 4: Exhaustion indicator | |
| 778 | + if (gamePhase === 'DAWN' && isExhausted) { | |
| 779 | + push(); | |
| 780 | + textAlign(CENTER); | |
| 781 | + textSize(16); | |
| 782 | + fill(255, 100, 100, 200 + sin(frameCount * 0.2) * 55); | |
| 783 | + stroke(0); | |
| 784 | + strokeWeight(2); | |
| 785 | + text("NO STAMINA!", spider.pos.x, spider.pos.y - 30); | |
| 786 | + pop(); | |
| 787 | + } | |
| 788 | + | |
| 789 | + // PHASE 4: Update and display birds during dawn | |
| 790 | + if (gamePhase === 'DAWN') { | |
| 791 | + // Update stamina | |
| 792 | + if (!spider.isAirborne && spider.vel.mag() < 0.1) { | |
| 793 | + // Resting - faster regen | |
| 794 | + jumpStamina += staminaRegenRate * 2.5; | |
| 795 | + } else { | |
| 796 | + // Moving - normal regen | |
| 797 | + jumpStamina += staminaRegenRate; | |
| 798 | + } | |
| 799 | + jumpStamina = min(jumpStamina, maxJumpStamina); | |
| 800 | + isExhausted = jumpStamina < jumpCost; | |
| 801 | + | |
| 802 | + // Update birds | |
| 803 | + for (let bird of birds) { | |
| 804 | + bird.update(); | |
| 805 | + bird.display(); | |
| 806 | + } | |
| 807 | + } | |
| 808 | + | |
| 809 | + // PHASE 4B: Update thief birds during night | |
| 810 | + if (gamePhase === 'NIGHT') { | |
| 811 | + for (let i = birds.length - 1; i >= 0; i--) { | |
| 812 | + let bird = birds[i]; | |
| 813 | + bird.update(); | |
| 814 | + bird.display(); | |
| 815 | + | |
| 816 | + // Remove inactive thief birds | |
| 817 | + if (bird.isThief && !bird.active) { | |
| 818 | + birds.splice(i, 1); | |
| 819 | + } | |
| 820 | + } | |
| 821 | + } | |
| 822 | + | |
| 823 | + // PHASE 3: Spider Sense - show fly path predictions | |
| 824 | + if (upgrades.spiderSense && upgrades.spiderSense.level > 0) { | |
| 825 | + push(); | |
| 826 | + strokeWeight(1); | |
| 827 | + for (let fly of flies) { | |
| 828 | + if (!fly.stuck && !fly.caught) { | |
| 829 | + // Predict future position | |
| 830 | + let futurePos = p5.Vector.add(fly.pos, p5.Vector.mult(fly.vel, 30)); | |
| 831 | + stroke(255, 255, 255, 30); | |
| 832 | + line(fly.pos.x, fly.pos.y, futurePos.x, futurePos.y); | |
| 833 | + noFill(); | |
| 834 | + stroke(255, 255, 255, 20); | |
| 835 | + ellipse(futurePos.x, futurePos.y, 10); | |
| 836 | + } | |
| 837 | + } | |
| 838 | + pop(); | |
| 839 | + } | |
| 840 | + | |
| 841 | + // PHASE 2: Display notifications | |
| 842 | + for (let i = notifications.length - 1; i >= 0; i--) { | |
| 843 | + notifications[i].update(); | |
| 844 | + notifications[i].display(); | |
| 845 | + if (notifications[i].isDead()) { | |
| 846 | + notifications.splice(i, 1); | |
| 847 | + } | |
| 848 | + } | |
| 849 | + | |
| 850 | + // PHASE 5: Display achievements | |
| 851 | + displayAchievements(); | |
| 852 | + | |
| 417 | 853 | // Update resources |
| 418 | 854 | updateResources(); |
| 419 | 855 | |
| 856 | + // PHASE 5: Check achievements continuously | |
| 857 | + checkAchievements(); | |
| 858 | + | |
| 859 | + // PHASE 3: Update jump charging | |
| 860 | + if (chargingJump && !spider.isAirborne) { | |
| 861 | + jumpChargeTime++; | |
| 862 | + spider.jumpChargeVisual = min(jumpChargeTime / maxJumpCharge, 1); | |
| 863 | + } else { | |
| 864 | + spider.jumpChargeVisual = 0; | |
| 865 | + } | |
| 866 | + | |
| 420 | 867 | // Handle web deployment |
| 421 | 868 | handleWebDeployment(); |
| 422 | 869 | |
| 423 | 870 | // Update UI |
| 424 | 871 | updateUI(); |
| 425 | 872 | |
| 426 | - // Spawn entities during night | |
| 873 | + // Spawn entities during night - PHASE 1 UPDATE | |
| 427 | 874 | if (gamePhase === 'NIGHT') { |
| 428 | - if (phaseTimer % 120 === 0 && flies.length < 15) { | |
| 429 | - flies.push(new Fly()); | |
| 875 | + // Dynamic spawn rate based on difficulty | |
| 876 | + let spawnRate = max(90, 120 - currentNight * 5); // Faster spawning over time | |
| 877 | + if (phaseTimer % spawnRate === 0 && flies.length < 10 + currentNight * 2) { | |
| 878 | + // PHASE 2: Spawn different types during the night too | |
| 879 | + let flyType = 'regular'; | |
| 880 | + let roll = random(); | |
| 881 | + | |
| 882 | + if (currentNight >= 5 && roll < 0.03) { | |
| 883 | + flyType = 'queen'; | |
| 884 | + } else if (roll < 0.08) { | |
| 885 | + flyType = 'golden'; | |
| 886 | + } else if (roll < 0.20) { | |
| 887 | + flyType = 'moth'; | |
| 888 | + } | |
| 889 | + | |
| 890 | + let fly = new Fly(flyType); | |
| 891 | + let speedMult = 1 + Math.floor((currentNight - 1) / 3) * 0.1; | |
| 892 | + fly.baseSpeed = baseFlySpeed * speedMult; | |
| 893 | + if (flyType === 'golden') fly.baseSpeed *= 1.3; | |
| 894 | + if (flyType === 'moth') fly.baseSpeed *= 0.8; | |
| 895 | + if (flyType === 'queen') fly.baseSpeed *= 0.5; | |
| 896 | + fly.currentSpeed = fly.baseSpeed; | |
| 897 | + flies.push(fly); | |
| 430 | 898 | } |
| 431 | 899 | if (phaseTimer % 300 === 0 && foodBoxes.length < 6) { |
| 432 | 900 | spawnFoodBox(); |
| 433 | 901 | } |
| 902 | + | |
| 903 | + // PHASE 4B: Spawn thief birds at night (after Night 5) | |
| 904 | + if (currentNight >= 5) { | |
| 905 | + thiefBirdTimer++; | |
| 906 | + if (thiefBirdTimer >= nextThiefTime) { | |
| 907 | + spawnThiefBird(); | |
| 908 | + thiefBirdTimer = 0; | |
| 909 | + nextThiefTime = random(2700, 3600); // 45-60 seconds | |
| 910 | + } | |
| 911 | + } | |
| 912 | + | |
| 913 | + // PHASE 4B: Random wind gusts at night | |
| 914 | + if (!windActive && frameCount > nextWindTime) { | |
| 915 | + startWindGust(); | |
| 916 | + } | |
| 917 | + } | |
| 918 | +} | |
| 919 | + | |
| 920 | +function openStatsPanel() { | |
| 921 | + // Update stats display | |
| 922 | + let statsHTML = ` | |
| 923 | + <div>Total Flies Caught: ${stats.totalFliesCaught}</div> | |
| 924 | + <div>Regular: ${stats.regularCaught}</div> | |
| 925 | + <div>Golden: ${stats.goldenCaught}</div> | |
| 926 | + <div>Moths: ${stats.mothsCaught}</div> | |
| 927 | + <div>Queens: ${stats.queensCaught}</div> | |
| 928 | + <div>Longest Night: ${stats.longestNight}</div> | |
| 929 | + <div>Total Jumps: ${stats.totalJumps}</div> | |
| 930 | + <div>Wind Jumps: ${stats.windJumps}</div> | |
| 931 | + <div>Thieves Scared: ${stats.thievesScared}</div> | |
| 932 | + <div>Perfect Dawns: ${stats.perfectDawns}</div> | |
| 933 | + `; | |
| 934 | + document.getElementById('stats-list').innerHTML = statsHTML; | |
| 935 | + | |
| 936 | + // Update skins display | |
| 937 | + let skinsHTML = ''; | |
| 938 | + let skins = [ | |
| 939 | + { id: 'default', name: 'Classic', icon: '🕷️', unlocked: true }, | |
| 940 | + { id: 'galaxy', name: 'Galaxy', icon: '🌌', unlocked: unlockedSkins.galaxy }, | |
| 941 | + { id: 'golden', name: 'Golden', icon: '✨', unlocked: unlockedSkins.golden }, | |
| 942 | + { id: 'shadow', name: 'Shadow', icon: '🌑', unlocked: unlockedSkins.shadow }, | |
| 943 | + { id: 'rainbow', name: 'Rainbow', icon: '🌈', unlocked: unlockedSkins.rainbow } | |
| 944 | + ]; | |
| 945 | + | |
| 946 | + for (let skin of skins) { | |
| 947 | + let selected = currentSkin === skin.id; | |
| 948 | + let locked = !skin.unlocked; | |
| 949 | + skinsHTML += ` | |
| 950 | + <div onclick="selectSkin('${skin.id}')" | |
| 951 | + style="padding: 10px; background: ${selected ? '#FFD700' : locked ? '#444' : '#666'}; | |
| 952 | + border-radius: 10px; cursor: ${locked ? 'not-allowed' : 'pointer'}; | |
| 953 | + opacity: ${locked ? '0.5' : '1'}; text-align: center;"> | |
| 954 | + <div style="font-size: 30px;">${skin.icon}</div> | |
| 955 | + <div style="font-size: 12px; color: ${selected ? '#000' : '#FFF'};"> | |
| 956 | + ${skin.name}${locked ? ' 🔒' : ''} | |
| 957 | + </div> | |
| 958 | + </div> | |
| 959 | + `; | |
| 960 | + } | |
| 961 | + document.getElementById('skins-list').innerHTML = skinsHTML; | |
| 962 | + | |
| 963 | + // Update achievements display | |
| 964 | + let achievementsHTML = ''; | |
| 965 | + for (let key in achievements) { | |
| 966 | + let ach = achievements[key]; | |
| 967 | + let progress = ach.progress !== undefined ? ` (${ach.progress}/${ach.target})` : ''; | |
| 968 | + achievementsHTML += ` | |
| 969 | + <div style="padding: 8px; background: ${ach.unlocked ? '#4CAF50' : '#444'}; | |
| 970 | + border-radius: 5px; opacity: ${ach.unlocked ? '1' : '0.6'};"> | |
| 971 | + ${ach.icon} ${ach.name}${!ach.unlocked ? progress : ' ✓'} | |
| 972 | + </div> | |
| 973 | + `; | |
| 974 | + } | |
| 975 | + document.getElementById('achievements-list').innerHTML = achievementsHTML; | |
| 976 | + | |
| 977 | + // Show panel | |
| 978 | + document.getElementById('stats-panel').style.display = 'block'; | |
| 979 | + | |
| 980 | + // Add close button listener | |
| 981 | + document.getElementById('close-stats-btn').onclick = () => { | |
| 982 | + document.getElementById('stats-panel').style.display = 'none'; | |
| 983 | + }; | |
| 984 | +} | |
| 985 | + | |
| 986 | +// Make selectSkin global | |
| 987 | +window.selectSkin = function(skinId) { | |
| 988 | + if (unlockedSkins[skinId]) { | |
| 989 | + currentSkin = skinId; | |
| 990 | + saveGame(); | |
| 991 | + openStatsPanel(); // Refresh display | |
| 992 | + notifications.push(new Notification(`Skin changed to ${skinId}!`, color(100, 255, 100))); | |
| 993 | + } | |
| 994 | +} | |
| 995 | + | |
| 996 | +// ============================================ | |
| 997 | +// PHASE 5: ACHIEVEMENTS & COSMETICS | |
| 998 | +// ============================================ | |
| 999 | + | |
| 1000 | +function checkAchievements() { | |
| 1001 | + // Night Owl - Survive X nights | |
| 1002 | + if (!achievements.nightOwl.unlocked) { | |
| 1003 | + achievements.nightOwl.progress = nightsSurvived; | |
| 1004 | + if (nightsSurvived >= achievements.nightOwl.target) { | |
| 1005 | + unlockAchievement('nightOwl'); | |
| 1006 | + } | |
| 1007 | + } | |
| 1008 | + | |
| 1009 | + // Silk Master - 15+ strands at once | |
| 1010 | + if (!achievements.silkMaster.unlocked) { | |
| 1011 | + let activeStrands = webStrands.filter(s => !s.broken).length; | |
| 1012 | + achievements.silkMaster.progress = max(achievements.silkMaster.progress, activeStrands); | |
| 1013 | + if (activeStrands >= achievements.silkMaster.target) { | |
| 1014 | + unlockAchievement('silkMaster'); | |
| 1015 | + } | |
| 1016 | + } | |
| 1017 | + | |
| 1018 | + // Wind Rider - Jump during wind | |
| 1019 | + if (!achievements.windRider.unlocked && achievements.windRider.progress >= achievements.windRider.target) { | |
| 1020 | + unlockAchievement('windRider'); | |
| 1021 | + } | |
| 1022 | + | |
| 1023 | + // Thief Defender | |
| 1024 | + if (!achievements.thiefDefender.unlocked && stats.thievesScared >= achievements.thiefDefender.target) { | |
| 1025 | + achievements.thiefDefender.progress = stats.thievesScared; | |
| 1026 | + unlockAchievement('thiefDefender'); | |
| 1027 | + } | |
| 1028 | + | |
| 1029 | + // Queen Slayer | |
| 1030 | + if (!achievements.queenSlayer.unlocked) { | |
| 1031 | + achievements.queenSlayer.progress = stats.queensCaught; | |
| 1032 | + if (stats.queensCaught >= achievements.queenSlayer.target) { | |
| 1033 | + unlockAchievement('queenSlayer'); | |
| 1034 | + } | |
| 1035 | + } | |
| 1036 | + | |
| 1037 | + // Galaxy Unlock - 15 nights | |
| 1038 | + if (!achievements.galaxyUnlock.unlocked) { | |
| 1039 | + achievements.galaxyUnlock.progress = nightsSurvived; | |
| 1040 | + if (nightsSurvived >= achievements.galaxyUnlock.target) { | |
| 1041 | + unlockAchievement('galaxyUnlock'); | |
| 1042 | + unlockedSkins.galaxy = true; | |
| 1043 | + } | |
| 1044 | + } | |
| 1045 | + | |
| 1046 | + // Golden Hunter - 100 golden flies | |
| 1047 | + if (!achievements.goldenHunter.unlocked) { | |
| 1048 | + achievements.goldenHunter.progress = stats.goldenCaught; | |
| 1049 | + if (stats.goldenCaught >= achievements.goldenHunter.target) { | |
| 1050 | + unlockAchievement('goldenHunter'); | |
| 1051 | + unlockedSkins.golden = true; | |
| 1052 | + } | |
| 1053 | + } | |
| 1054 | + | |
| 1055 | + // Web Master - 500 total flies | |
| 1056 | + if (!achievements.webMaster.unlocked) { | |
| 1057 | + achievements.webMaster.progress = stats.totalFliesCaught; | |
| 1058 | + if (stats.totalFliesCaught >= achievements.webMaster.target) { | |
| 1059 | + unlockAchievement('webMaster'); | |
| 1060 | + unlockedSkins.rainbow = true; | |
| 1061 | + } | |
| 1062 | + } | |
| 1063 | + | |
| 1064 | + // Speedrunner - 30 flies before night 5 | |
| 1065 | + if (!achievements.speedrunner.unlocked && currentNight < 5 && stats.totalFliesCaught >= 30) { | |
| 1066 | + unlockAchievement('speedrunner'); | |
| 1067 | + } | |
| 1068 | +} | |
| 1069 | + | |
| 1070 | +function checkNightAchievements() { | |
| 1071 | + // Called at end of night | |
| 1072 | + | |
| 1073 | + // Feast - 20 flies munched in one night | |
| 1074 | + if (!achievements.feast.unlocked && stats.fliesMunchedInCurrentNight >= achievements.feast.target) { | |
| 1075 | + achievements.feast.progress = stats.fliesMunchedInCurrentNight; | |
| 1076 | + unlockAchievement('feast'); | |
| 1077 | + } | |
| 1078 | + | |
| 1079 | + // Architect - Catch 5 flies without munching | |
| 1080 | + if (!achievements.architect.unlocked && stats.fliesCaughtWithoutMunch >= achievements.architect.target) { | |
| 1081 | + achievements.architect.progress = stats.fliesCaughtWithoutMunch; | |
| 1082 | + unlockAchievement('architect'); | |
| 1083 | + } | |
| 1084 | + | |
| 1085 | + // Untouchable - No strands lost | |
| 1086 | + if (!achievements.untouchable.unlocked && stats.strandsLostInNight === 0) { | |
| 1087 | + unlockAchievement('untouchable'); | |
| 1088 | + } | |
| 1089 | + | |
| 1090 | + // Shadow Predator - 50 flies in one night | |
| 1091 | + if (!achievements.shadowPredator.unlocked && fliesCaught >= achievements.shadowPredator.target) { | |
| 1092 | + achievements.shadowPredator.progress = fliesCaught; | |
| 1093 | + unlockAchievement('shadowPredator'); | |
| 1094 | + unlockedSkins.shadow = true; | |
| 1095 | + } | |
| 1096 | + | |
| 1097 | + // Reset night-specific counters | |
| 1098 | + stats.fliesMunchedInCurrentNight = 0; | |
| 1099 | + stats.fliesCaughtWithoutMunch = fliesCaught; | |
| 1100 | + stats.strandsLostInNight = 0; | |
| 1101 | +} | |
| 1102 | + | |
| 1103 | +function checkDawnAchievements() { | |
| 1104 | + // Perfect Dawn - no bird hits | |
| 1105 | + if (!achievements.perfectDawn.unlocked && stats.birdHitsTaken === 0) { | |
| 1106 | + unlockAchievement('perfectDawn'); | |
| 1107 | + stats.perfectDawns++; | |
| 1108 | + } | |
| 1109 | + | |
| 1110 | + // Exhaustion Master - survive with < 20 stamina | |
| 1111 | + if (!achievements.exhaustionMaster.unlocked && jumpStamina < 20) { | |
| 1112 | + unlockAchievement('exhaustionMaster'); | |
| 1113 | + } | |
| 1114 | + | |
| 1115 | + // Reset dawn counter | |
| 1116 | + stats.birdHitsTaken = 0; | |
| 1117 | +} | |
| 1118 | + | |
| 1119 | +function unlockAchievement(achievementKey) { | |
| 1120 | + let achievement = achievements[achievementKey]; | |
| 1121 | + if (achievement.unlocked) return; | |
| 1122 | + | |
| 1123 | + achievement.unlocked = true; | |
| 1124 | + achievementQueue.push(achievement); | |
| 1125 | + | |
| 1126 | + // Save to localStorage | |
| 1127 | + saveGame(); | |
| 1128 | +} | |
| 1129 | + | |
| 1130 | +function displayAchievements() { | |
| 1131 | + // Show queued achievements | |
| 1132 | + if (!showingAchievement && achievementQueue.length > 0) { | |
| 1133 | + showingAchievement = achievementQueue.shift(); | |
| 1134 | + achievementDisplayTimer = 240; // 4 seconds | |
| 1135 | + } | |
| 1136 | + | |
| 1137 | + // Display current achievement | |
| 1138 | + if (showingAchievement && achievementDisplayTimer > 0) { | |
| 1139 | + push(); | |
| 1140 | + | |
| 1141 | + // Background | |
| 1142 | + let alpha = achievementDisplayTimer > 200 ? 255 : map(achievementDisplayTimer, 0, 40, 0, 255); | |
| 1143 | + fill(20, 20, 40, alpha * 0.9); | |
| 1144 | + stroke(255, 215, 0, alpha); | |
| 1145 | + strokeWeight(3); | |
| 1146 | + rectMode(CENTER); | |
| 1147 | + rect(width / 2, 100, 400, 80, 10); | |
| 1148 | + | |
| 1149 | + // Icon | |
| 1150 | + textAlign(CENTER); | |
| 1151 | + textSize(30); | |
| 1152 | + fill(255, 255, 255, alpha); | |
| 1153 | + text(showingAchievement.icon, width / 2 - 150, 105); | |
| 1154 | + | |
| 1155 | + // Text | |
| 1156 | + textSize(20); | |
| 1157 | + fill(255, 215, 0, alpha); | |
| 1158 | + text("ACHIEVEMENT UNLOCKED!", width / 2, 85); | |
| 1159 | + | |
| 1160 | + textSize(16); | |
| 1161 | + fill(255, 255, 255, alpha); | |
| 1162 | + text(showingAchievement.name, width / 2, 105); | |
| 1163 | + | |
| 1164 | + textSize(12); | |
| 1165 | + fill(200, 200, 200, alpha); | |
| 1166 | + text(showingAchievement.desc, width / 2, 125); | |
| 1167 | + | |
| 1168 | + pop(); | |
| 1169 | + | |
| 1170 | + achievementDisplayTimer--; | |
| 1171 | + if (achievementDisplayTimer <= 0) { | |
| 1172 | + showingAchievement = null; | |
| 1173 | + } | |
| 434 | 1174 | } |
| 435 | 1175 | } |
| 436 | 1176 | |
| 1177 | +function saveGame() { | |
| 1178 | + // Save to localStorage | |
| 1179 | + let saveData = { | |
| 1180 | + achievements: achievements, | |
| 1181 | + stats: stats, | |
| 1182 | + unlockedSkins: unlockedSkins, | |
| 1183 | + currentSkin: currentSkin, | |
| 1184 | + upgrades: upgrades, | |
| 1185 | + playerPoints: playerPoints, | |
| 1186 | + nightsSurvived: nightsSurvived, | |
| 1187 | + currentNight: currentNight | |
| 1188 | + }; | |
| 1189 | + | |
| 1190 | + localStorage.setItem('cobGameSave', JSON.stringify(saveData)); | |
| 1191 | +} | |
| 1192 | + | |
| 1193 | +function loadGame() { | |
| 1194 | + let saveData = localStorage.getItem('cobGameSave'); | |
| 1195 | + if (saveData) { | |
| 1196 | + let data = JSON.parse(saveData); | |
| 1197 | + achievements = data.achievements || achievements; | |
| 1198 | + stats = data.stats || stats; | |
| 1199 | + unlockedSkins = data.unlockedSkins || unlockedSkins; | |
| 1200 | + currentSkin = data.currentSkin || 'default'; | |
| 1201 | + upgrades = data.upgrades || upgrades; | |
| 1202 | + playerPoints = data.playerPoints || 0; | |
| 1203 | + nightsSurvived = data.nightsSurvived || 0; | |
| 1204 | + currentNight = data.currentNight || 1; | |
| 1205 | + | |
| 1206 | + // Apply upgrades | |
| 1207 | + applyUpgradeEffects(); | |
| 1208 | + } | |
| 1209 | +} | |
| 1210 | + | |
| 1211 | +// ============================================ | |
| 1212 | +// PHASE 4B: NIGHT THREATS | |
| 1213 | +// ============================================ | |
| 1214 | + | |
| 1215 | +function spawnThiefBird() { | |
| 1216 | + // Check if there are caught flies to steal | |
| 1217 | + let caughtFlies = flies.filter(f => f.stuck || f.caught); | |
| 1218 | + if (caughtFlies.length === 0) return; | |
| 1219 | + | |
| 1220 | + // Create a thief bird | |
| 1221 | + let thief = new Bird('swoop', true); | |
| 1222 | + thief.active = true; | |
| 1223 | + thief.attackDelay = 60; // Attack quickly | |
| 1224 | + birds.push(thief); | |
| 1225 | + | |
| 1226 | + // PHASE 5: Track thief scared if spider is near | |
| 1227 | + if (dist(spider.pos.x, spider.pos.y, caughtFlies[0].pos.x, caughtFlies[0].pos.y) < 80) { | |
| 1228 | + stats.thievesScared++; | |
| 1229 | + } | |
| 1230 | + | |
| 1231 | + // Visual warning | |
| 1232 | + push(); | |
| 1233 | + textAlign(CENTER); | |
| 1234 | + textSize(30); | |
| 1235 | + fill(200, 50, 200); | |
| 1236 | + stroke(0); | |
| 1237 | + strokeWeight(3); | |
| 1238 | + text("THIEF!", width / 2, height / 2); | |
| 1239 | + pop(); | |
| 1240 | +} | |
| 1241 | + | |
| 1242 | +function startWindGust() { | |
| 1243 | + windActive = true; | |
| 1244 | + windDirection = random() < 0.5 ? 0 : PI; // Left or right | |
| 1245 | + windStrength = random(2, 5); // Variable strength | |
| 1246 | + windDuration = random(300, 600); // 5-10 seconds | |
| 1247 | + windTimer = 0; | |
| 1248 | + windParticles = []; | |
| 1249 | + | |
| 1250 | + // Notification | |
| 1251 | + let direction = windDirection === 0 ? "→" : "←"; | |
| 1252 | + notifications.push(new Notification(`Wind gust ${direction}`, color(200, 200, 255))); | |
| 1253 | +} | |
| 1254 | + | |
| 1255 | +function updateWind() { | |
| 1256 | + if (!windActive) return; | |
| 1257 | + | |
| 1258 | + windTimer++; | |
| 1259 | + | |
| 1260 | + // Fade in and out | |
| 1261 | + if (windTimer < 60) { | |
| 1262 | + // Fade in | |
| 1263 | + windStrength = lerp(0, windStrength, windTimer / 60); | |
| 1264 | + } else if (windTimer > windDuration - 60) { | |
| 1265 | + // Fade out | |
| 1266 | + windStrength = lerp(windStrength, 0, (windTimer - (windDuration - 60)) / 60); | |
| 1267 | + } | |
| 1268 | + | |
| 1269 | + // End wind | |
| 1270 | + if (windTimer >= windDuration) { | |
| 1271 | + windActive = false; | |
| 1272 | + windTimer = 0; | |
| 1273 | + windParticles = []; | |
| 1274 | + nextWindTime = frameCount + random(1800, 3600); // 30-60 seconds until next wind | |
| 1275 | + } | |
| 1276 | +} | |
| 1277 | + | |
| 1278 | +// ============================================ | |
| 1279 | +// PHASE 4: DAWN SURVIVAL FUNCTIONS | |
| 1280 | +// ============================================ | |
| 1281 | + | |
| 1282 | +function spawnDawnBirds() { | |
| 1283 | + birds = []; | |
| 1284 | + | |
| 1285 | + // Start with 3 birds, add 1 every 3 nights (capped at 6) | |
| 1286 | + let numBirds = min(3 + Math.floor((currentNight - 1) / 3), 6); | |
| 1287 | + | |
| 1288 | + // Mix of attack patterns | |
| 1289 | + let patterns = ['dive', 'dive', 'glide']; // More dive birds | |
| 1290 | + if (currentNight >= 3) patterns.push('circle'); | |
| 1291 | + if (currentNight >= 6) patterns.push('dive', 'glide'); | |
| 1292 | + | |
| 1293 | + for (let i = 0; i < numBirds; i++) { | |
| 1294 | + let pattern = random(patterns); | |
| 1295 | + let bird = new Bird(pattern); | |
| 1296 | + bird.active = true; | |
| 1297 | + // Stagger attack delays | |
| 1298 | + bird.attackDelay = 60 + i * 60; // 1 second apart initially | |
| 1299 | + birds.push(bird); | |
| 1300 | + } | |
| 1301 | + | |
| 1302 | + // Notification | |
| 1303 | + notifications.push(new Notification(`DAWN! ${numBirds} birds hunting!`, color(255, 150, 100))); | |
| 1304 | +} | |
| 1305 | + | |
| 1306 | +// ============================================ | |
| 1307 | +// PHASE 3: UPGRADE SHOP FUNCTIONS | |
| 1308 | +// ============================================ | |
| 1309 | + | |
| 1310 | +function openUpgradeShop() { | |
| 1311 | + if (currentNight <= 1) return; // No shop on first night | |
| 1312 | + | |
| 1313 | + shopOpen = true; | |
| 1314 | + noLoop(); // Pause the game | |
| 1315 | + | |
| 1316 | + // Calculate points from flies caught this session | |
| 1317 | + playerPoints = totalFliesCaught; | |
| 1318 | + | |
| 1319 | + // Update shop UI | |
| 1320 | + document.getElementById('upgrade-shop').style.display = 'block'; | |
| 1321 | + document.getElementById('available-points').textContent = playerPoints; | |
| 1322 | + | |
| 1323 | + // Populate upgrade lists | |
| 1324 | + updateShopDisplay(); | |
| 1325 | + | |
| 1326 | + // Add continue button listener | |
| 1327 | + document.getElementById('continue-btn').onclick = closeUpgradeShop; | |
| 1328 | +} | |
| 1329 | + | |
| 1330 | +function closeUpgradeShop() { | |
| 1331 | + shopOpen = false; | |
| 1332 | + document.getElementById('upgrade-shop').style.display = 'none'; | |
| 1333 | + loop(); // Resume the game | |
| 1334 | +} | |
| 1335 | + | |
| 1336 | +function updateShopDisplay() { | |
| 1337 | + let tier1HTML = ''; | |
| 1338 | + let tier2HTML = ''; | |
| 1339 | + let tier1Count = 0; | |
| 1340 | + | |
| 1341 | + // Count tier 1 upgrades | |
| 1342 | + for (let key in upgrades) { | |
| 1343 | + if (upgrades[key].tier === 1 && upgrades[key].level > 0) { | |
| 1344 | + tier1Count++; | |
| 1345 | + } | |
| 1346 | + } | |
| 1347 | + | |
| 1348 | + // Display Tier 1 upgrades | |
| 1349 | + for (let key in upgrades) { | |
| 1350 | + let upgrade = upgrades[key]; | |
| 1351 | + if (upgrade.tier === 1) { | |
| 1352 | + let canAfford = playerPoints >= upgrade.cost; | |
| 1353 | + let maxed = upgrade.level >= upgrade.maxLevel; | |
| 1354 | + let buttonText = maxed ? 'MAXED' : `Buy (${upgrade.cost} pts)`; | |
| 1355 | + let buttonDisabled = maxed || !canAfford ? 'disabled' : ''; | |
| 1356 | + let opacity = maxed ? '0.5' : '1'; | |
| 1357 | + | |
| 1358 | + tier1HTML += ` | |
| 1359 | + <div style="margin: 10px 0; padding: 10px; background: rgba(0,0,0,0.3); | |
| 1360 | + border-radius: 10px; opacity: ${opacity};"> | |
| 1361 | + <div style="display: flex; justify-content: space-between; align-items: center;"> | |
| 1362 | + <div> | |
| 1363 | + <span style="font-size: 24px;">${upgrade.icon}</span> | |
| 1364 | + <strong>${upgrade.name}</strong> (${upgrade.level}/${upgrade.maxLevel}) | |
| 1365 | + <br><small>${upgrade.description}</small> | |
| 1366 | + </div> | |
| 1367 | + <button onclick="buyUpgrade('${key}')" ${buttonDisabled} | |
| 1368 | + style="padding: 5px 15px; background: ${canAfford && !maxed ? '#4CAF50' : '#666'}; | |
| 1369 | + color: white; border: none; border-radius: 5px; cursor: ${canAfford && !maxed ? 'pointer' : 'not-allowed'};"> | |
| 1370 | + ${buttonText} | |
| 1371 | + </button> | |
| 1372 | + </div> | |
| 1373 | + </div> | |
| 1374 | + `; | |
| 1375 | + } | |
| 1376 | + } | |
| 1377 | + | |
| 1378 | + // Display Tier 2 upgrades | |
| 1379 | + for (let key in upgrades) { | |
| 1380 | + let upgrade = upgrades[key]; | |
| 1381 | + if (upgrade.tier === 2) { | |
| 1382 | + let unlocked = tier1Count >= upgrade.requires; | |
| 1383 | + let canAfford = playerPoints >= upgrade.cost && unlocked; | |
| 1384 | + let maxed = upgrade.level >= upgrade.maxLevel; | |
| 1385 | + let buttonText = maxed ? 'MAXED' : !unlocked ? `Needs ${upgrade.requires} Tier 1` : `Buy (${upgrade.cost} pts)`; | |
| 1386 | + let buttonDisabled = maxed || !canAfford ? 'disabled' : ''; | |
| 1387 | + let opacity = !unlocked ? '0.3' : maxed ? '0.5' : '1'; | |
| 1388 | + | |
| 1389 | + tier2HTML += ` | |
| 1390 | + <div style="margin: 10px 0; padding: 10px; background: rgba(0,0,0,0.3); | |
| 1391 | + border-radius: 10px; opacity: ${opacity};"> | |
| 1392 | + <div style="display: flex; justify-content: space-between; align-items: center;"> | |
| 1393 | + <div> | |
| 1394 | + <span style="font-size: 24px;">${upgrade.icon}</span> | |
| 1395 | + <strong>${upgrade.name}</strong> (${upgrade.level}/${upgrade.maxLevel}) | |
| 1396 | + <br><small>${upgrade.description}</small> | |
| 1397 | + </div> | |
| 1398 | + <button onclick="buyUpgrade('${key}')" ${buttonDisabled} | |
| 1399 | + style="padding: 5px 15px; background: ${canAfford && !maxed ? '#FF69B4' : '#666'}; | |
| 1400 | + color: white; border: none; border-radius: 5px; cursor: ${canAfford && !maxed ? 'pointer' : 'not-allowed'};"> | |
| 1401 | + ${buttonText} | |
| 1402 | + </button> | |
| 1403 | + </div> | |
| 1404 | + </div> | |
| 1405 | + `; | |
| 1406 | + } | |
| 1407 | + } | |
| 1408 | + | |
| 1409 | + document.getElementById('upgrade-list-tier1').innerHTML = tier1HTML; | |
| 1410 | + document.getElementById('upgrade-list-tier2').innerHTML = tier2HTML; | |
| 1411 | + | |
| 1412 | + // Update tier 2 section opacity | |
| 1413 | + document.getElementById('tier2-upgrades').style.opacity = tier1Count >= 2 ? '1' : '0.5'; | |
| 1414 | +} | |
| 1415 | + | |
| 1416 | +// Make buyUpgrade global so onclick can access it | |
| 1417 | +window.buyUpgrade = function(upgradeKey) { | |
| 1418 | + let upgrade = upgrades[upgradeKey]; | |
| 1419 | + if (!upgrade) return; | |
| 1420 | + | |
| 1421 | + // Check tier requirements | |
| 1422 | + if (upgrade.tier === 2) { | |
| 1423 | + let tier1Count = 0; | |
| 1424 | + for (let key in upgrades) { | |
| 1425 | + if (upgrades[key].tier === 1 && upgrades[key].level > 0) { | |
| 1426 | + tier1Count++; | |
| 1427 | + } | |
| 1428 | + } | |
| 1429 | + if (tier1Count < upgrade.requires) return; | |
| 1430 | + } | |
| 1431 | + | |
| 1432 | + // Check if can afford and not maxed | |
| 1433 | + if (playerPoints >= upgrade.cost && upgrade.level < upgrade.maxLevel) { | |
| 1434 | + playerPoints -= upgrade.cost; | |
| 1435 | + upgrade.level++; | |
| 1436 | + | |
| 1437 | + // Apply upgrade effects immediately | |
| 1438 | + applyUpgradeEffects(); | |
| 1439 | + | |
| 1440 | + // Update display | |
| 1441 | + document.getElementById('available-points').textContent = playerPoints; | |
| 1442 | + updateShopDisplay(); | |
| 1443 | + | |
| 1444 | + // Show notification | |
| 1445 | + notifications.push(new Notification(`Upgraded ${upgrade.name}!`, color(100, 255, 100))); | |
| 1446 | + } | |
| 1447 | +} | |
| 1448 | + | |
| 1449 | +function applyUpgradeEffects() { | |
| 1450 | + // Reset to base values | |
| 1451 | + spider.jumpPower = 12; | |
| 1452 | + maxWebSilk = 100; | |
| 1453 | + silkDrainRate = 2; | |
| 1454 | + spider.munchCooldownMax = 30; // Add this property to spider | |
| 1455 | + | |
| 1456 | + // Apply Tier 1 upgrades | |
| 1457 | + if (upgrades.strongLegs.level > 0) { | |
| 1458 | + spider.jumpPower = 12 * (1 + 0.15 * upgrades.strongLegs.level); | |
| 1459 | + } | |
| 1460 | + | |
| 1461 | + if (upgrades.silkGlands.level > 0) { | |
| 1462 | + maxWebSilk = 100 + (20 * upgrades.silkGlands.level); | |
| 1463 | + webSilk = min(webSilk, maxWebSilk); // Cap current silk to new max | |
| 1464 | + } | |
| 1465 | + | |
| 1466 | + if (upgrades.efficientSpinning.level > 0) { | |
| 1467 | + silkDrainRate = 2 * (1 - 0.2 * upgrades.efficientSpinning.level); | |
| 1468 | + } | |
| 1469 | + | |
| 1470 | + if (upgrades.quickMunch.level > 0) { | |
| 1471 | + spider.munchCooldownMax = 30 * (1 - 0.3 * upgrades.quickMunch.level); | |
| 1472 | + } | |
| 1473 | + | |
| 1474 | + // Tier 2 upgrades are handled in their respective functions | |
| 1475 | +} | |
| 1476 | + | |
| 1477 | +function spawnNightFlies() { | |
| 1478 | + // Base flies + more per night | |
| 1479 | + let numFlies = 5 + currentNight; | |
| 1480 | + | |
| 1481 | + // Apply difficulty scaling | |
| 1482 | + let flySpeedMultiplier = 1 + Math.floor((currentNight - 1) / 3) * 0.1; // +10% every 3 nights | |
| 1483 | + | |
| 1484 | + for (let i = 0; i < numFlies; i++) { | |
| 1485 | + // PHASE 2: Spawn different fly types with rarity | |
| 1486 | + let flyType = 'regular'; | |
| 1487 | + let roll = random(); | |
| 1488 | + | |
| 1489 | + if (currentNight >= 5 && roll < 0.05) { | |
| 1490 | + // Queen flies: 5% chance after night 5 | |
| 1491 | + flyType = 'queen'; | |
| 1492 | + } else if (roll < 0.1) { | |
| 1493 | + // Golden flies: 10% chance | |
| 1494 | + flyType = 'golden'; | |
| 1495 | + } else if (roll < 0.25) { | |
| 1496 | + // Moths: 15% chance | |
| 1497 | + flyType = 'moth'; | |
| 1498 | + } | |
| 1499 | + | |
| 1500 | + let fly = new Fly(flyType); | |
| 1501 | + fly.baseSpeed = baseFlySpeed * flySpeedMultiplier; | |
| 1502 | + if (flyType === 'golden') fly.baseSpeed *= 1.3; // Golden are always faster | |
| 1503 | + if (flyType === 'moth') fly.baseSpeed *= 0.8; // Moths are slower | |
| 1504 | + if (flyType === 'queen') fly.baseSpeed *= 0.5; // Queens are much slower | |
| 1505 | + fly.currentSpeed = fly.baseSpeed; | |
| 1506 | + flies.push(fly); | |
| 1507 | + } | |
| 1508 | + | |
| 1509 | + // PHASE 2: Guarantee at least 1 golden fly per night | |
| 1510 | + if (flies.filter(f => f.type === 'golden').length === 0) { | |
| 1511 | + let goldenFly = new Fly('golden'); | |
| 1512 | + goldenFly.baseSpeed = baseFlySpeed * flySpeedMultiplier * 1.3; | |
| 1513 | + goldenFly.currentSpeed = goldenFly.baseSpeed; | |
| 1514 | + flies.push(goldenFly); | |
| 1515 | + // Add notification | |
| 1516 | + notifications.push(new Notification("Golden Firefly Appeared! ✨", color(255, 215, 0))); | |
| 1517 | + } | |
| 1518 | + | |
| 1519 | + // PHASE 2: Guarantee a queen on nights 10+ | |
| 1520 | + if (currentNight >= 10 && flies.filter(f => f.type === 'queen').length === 0) { | |
| 1521 | + let queenFly = new Fly('queen'); | |
| 1522 | + queenFly.baseSpeed = baseFlySpeed * flySpeedMultiplier * 0.5; | |
| 1523 | + queenFly.currentSpeed = queenFly.baseSpeed; | |
| 1524 | + flies.push(queenFly); | |
| 1525 | + // Add notification | |
| 1526 | + notifications.push(new Notification("Queen Firefly Arrived! 👑", color(200, 100, 255))); | |
| 1527 | + } | |
| 1528 | + | |
| 1529 | + // Spawn some food boxes | |
| 1530 | + for (let i = 0; i < 3; i++) { | |
| 1531 | + spawnFoodBox(); | |
| 1532 | + } | |
| 1533 | +} | |
| 1534 | + | |
| 1535 | +function escapeFlies() { | |
| 1536 | + // Store escaping flies (could be used for visual effect later) | |
| 1537 | + fliesEscaped = []; | |
| 1538 | + | |
| 1539 | + for (let fly of flies) { | |
| 1540 | + if (!fly.stuck) { | |
| 1541 | + fliesEscaped.push({ | |
| 1542 | + x: fly.pos.x, | |
| 1543 | + y: fly.pos.y, | |
| 1544 | + type: fly.type // PHASE 2: Store actual type | |
| 1545 | + }); | |
| 1546 | + } | |
| 1547 | + } | |
| 1548 | + | |
| 1549 | + // Clear all flies | |
| 1550 | + flies = []; | |
| 1551 | +} | |
| 1552 | + | |
| 1553 | +function degradeWebs() { | |
| 1554 | + // Degrade each web strand by 10% | |
| 1555 | + for (let strand of webStrands) { | |
| 1556 | + strand.strength *= 0.9; | |
| 1557 | + | |
| 1558 | + // Very weak strands break | |
| 1559 | + if (strand.strength < 0.3) { | |
| 1560 | + strand.broken = true; | |
| 1561 | + } | |
| 1562 | + | |
| 1563 | + // Add slight sag to simulate aging | |
| 1564 | + if (strand.path && strand.path.length > 2) { | |
| 1565 | + for (let i = 1; i < strand.path.length - 1; i++) { | |
| 1566 | + strand.path[i].y += random(2, 5); | |
| 1567 | + } | |
| 1568 | + } | |
| 1569 | + } | |
| 1570 | + | |
| 1571 | + // Create some particles to show degradation | |
| 1572 | + for (let i = 0; i < 10; i++) { | |
| 1573 | + let p = new Particle(random(width), random(height)); | |
| 1574 | + p.color = color(255, 255, 255, 100); | |
| 1575 | + p.vel = createVector(0, random(0.5, 2)); | |
| 1576 | + p.size = 2; | |
| 1577 | + particles.push(p); | |
| 1578 | + } | |
| 1579 | +} | |
| 1580 | + | |
| 1581 | +function prepareDusk() { | |
| 1582 | + // Return some flies for the next night (visual continuity) | |
| 1583 | + let returnCount = min(3, fliesEscaped.length); | |
| 1584 | + for (let i = 0; i < returnCount; i++) { | |
| 1585 | + // PHASE 2: Recreate the same type of fly that escaped | |
| 1586 | + let fly = new Fly(fliesEscaped[i].type); | |
| 1587 | + // Start from edge but move toward previous positions | |
| 1588 | + fly.wanderAngle = atan2( | |
| 1589 | + fliesEscaped[i].y - fly.pos.y, | |
| 1590 | + fliesEscaped[i].x - fly.pos.x | |
| 1591 | + ); | |
| 1592 | + flies.push(fly); | |
| 1593 | + } | |
| 1594 | +} | |
| 1595 | + | |
| 1596 | +function drawSun() { | |
| 1597 | + push(); | |
| 1598 | + noStroke(); | |
| 1599 | + | |
| 1600 | + // Sun glow | |
| 1601 | + fill(255, 230, 100, sunOpacity * 0.3); | |
| 1602 | + ellipse(width - 150, sunY, 120); | |
| 1603 | + fill(255, 220, 50, sunOpacity * 0.5); | |
| 1604 | + ellipse(width - 150, sunY, 80); | |
| 1605 | + fill(255, 200, 0, sunOpacity); | |
| 1606 | + ellipse(width - 150, sunY, 50); | |
| 1607 | + | |
| 1608 | + pop(); | |
| 1609 | +} | |
| 1610 | + | |
| 1611 | +// ============================================ | |
| 1612 | +// ORIGINAL FUNCTIONS WITH PHASE 1 UPDATES | |
| 1613 | +// ============================================ | |
| 1614 | + | |
| 437 | 1615 | function updateSkyColors() { |
| 438 | - if (gamePhase === 'DUSK') { | |
| 439 | - currentSkyColor1 = lerpColor(color(135, 206, 235), color(255, 140, 90), phaseTimer / DUSK_DURATION); | |
| 1616 | + // PHASE 1 - Complete rewrite for full cycle | |
| 1617 | + if (gamePhase === 'DAWN') { | |
| 1618 | + // Dawn: dark purple/blue to soft orange/pink | |
| 1619 | + currentSkyColor1 = lerpColor(color(70, 70, 120), color(255, 200, 150), phaseTimer / DAWN_DURATION); | |
| 1620 | + currentSkyColor2 = lerpColor(color(30, 30, 60), color(255, 150, 100), phaseTimer / DAWN_DURATION); | |
| 1621 | + moonOpacity = lerp(255, 0, phaseTimer / DAWN_DURATION); | |
| 1622 | + moonY = lerp(60, -50, phaseTimer / DAWN_DURATION); | |
| 1623 | + sunY = lerp(height + 50, height - 100, phaseTimer / DAWN_DURATION); | |
| 1624 | + sunOpacity = lerp(0, 100, phaseTimer / DAWN_DURATION); | |
| 1625 | + } else if (gamePhase === 'DAWN_TO_DAY') { | |
| 1626 | + let t = phaseTimer / TRANSITION_DURATION; | |
| 1627 | + currentSkyColor1 = lerpColor(color(255, 200, 150), color(135, 206, 235), t); | |
| 1628 | + currentSkyColor2 = lerpColor(color(255, 150, 100), color(255, 255, 200), t); | |
| 1629 | + sunY = lerp(height - 100, height * 0.3, t); | |
| 1630 | + sunOpacity = lerp(100, 255, t); | |
| 1631 | + } else if (gamePhase === 'DAY') { | |
| 1632 | + // Day: bright blue sky | |
| 1633 | + currentSkyColor1 = color(135, 206, 235); | |
| 1634 | + currentSkyColor2 = color(255, 255, 200); | |
| 1635 | + sunY = lerp(height * 0.3, 100, phaseTimer / DAY_DURATION); | |
| 1636 | + sunOpacity = 255; | |
| 1637 | + } else if (gamePhase === 'DAY_TO_DUSK') { | |
| 1638 | + let t = phaseTimer / TRANSITION_DURATION; | |
| 1639 | + currentSkyColor1 = lerpColor(color(135, 206, 235), color(255, 140, 90), t); | |
| 1640 | + currentSkyColor2 = lerpColor(color(255, 255, 200), color(255, 183, 77), t); | |
| 1641 | + sunY = lerp(100, 60, t); | |
| 1642 | + sunOpacity = lerp(255, 150, t); | |
| 1643 | + } else if (gamePhase === 'DUSK') { | |
| 1644 | + // Dusk: orange/purple sunset | |
| 1645 | + currentSkyColor1 = lerpColor(color(255, 140, 90), color(200, 100, 120), phaseTimer / DUSK_DURATION); | |
| 440 | 1646 | currentSkyColor2 = lerpColor(color(255, 183, 77), color(120, 60, 120), phaseTimer / DUSK_DURATION); |
| 441 | - } else if (gamePhase === 'TRANSITION') { | |
| 1647 | + sunY = lerp(60, -50, phaseTimer / DUSK_DURATION); | |
| 1648 | + sunOpacity = lerp(150, 0, phaseTimer / DUSK_DURATION); | |
| 1649 | + } else if (gamePhase === 'DUSK_TO_NIGHT') { | |
| 442 | 1650 | let t = phaseTimer / TRANSITION_DURATION; |
| 443 | - currentSkyColor1 = lerpColor(color(255, 140, 90), color(25, 25, 112), t); | |
| 1651 | + currentSkyColor1 = lerpColor(color(200, 100, 120), color(25, 25, 112), t); | |
| 444 | 1652 | currentSkyColor2 = lerpColor(color(120, 60, 120), color(0, 0, 40), t); |
| 445 | 1653 | moonOpacity = t * 255; |
| 446 | 1654 | moonY = lerp(100, 60, t); |
| 447 | 1655 | } else if (gamePhase === 'NIGHT') { |
| 1656 | + // Night: dark blue/purple | |
| 448 | 1657 | currentSkyColor1 = color(25, 25, 112); |
| 449 | 1658 | currentSkyColor2 = color(0, 0, 40); |
| 450 | 1659 | moonOpacity = 255; |
| 1660 | + moonY = 60; | |
| 1661 | + } else if (gamePhase === 'NIGHT_TO_DAWN') { | |
| 1662 | + let t = phaseTimer / TRANSITION_DURATION; | |
| 1663 | + currentSkyColor1 = lerpColor(color(25, 25, 112), color(70, 70, 120), t); | |
| 1664 | + currentSkyColor2 = lerpColor(color(0, 0, 40), color(30, 30, 60), t); | |
| 451 | 1665 | } |
| 452 | 1666 | } |
| 453 | 1667 | |
@@ -495,8 +1709,8 @@ function drawSkyGradient() { | ||
| 495 | 1709 | |
| 496 | 1710 | noStroke(); |
| 497 | 1711 | |
| 498 | - // Base color | |
| 499 | - if (gamePhase === 'NIGHT') { | |
| 1712 | + // Base color - PHASE 1: Update for all phases | |
| 1713 | + if (gamePhase === 'NIGHT' || gamePhase === 'NIGHT_TO_DAWN') { | |
| 500 | 1714 | fill(30, 15, 5); |
| 501 | 1715 | } else { |
| 502 | 1716 | fill(92, 51, 23); |
@@ -526,7 +1740,7 @@ function drawSkyGradient() { | ||
| 526 | 1740 | rotate((branch.side === 'right' ? -1 : 1) * PI/6); |
| 527 | 1741 | |
| 528 | 1742 | // Fork branch |
| 529 | - if (gamePhase === 'NIGHT') { | |
| 1743 | + if (gamePhase === 'NIGHT' || gamePhase === 'NIGHT_TO_DAWN') { | |
| 530 | 1744 | fill(35, 18, 6); |
| 531 | 1745 | } else { |
| 532 | 1746 | fill(102, 58, 28); |
@@ -541,7 +1755,7 @@ function drawSkyGradient() { | ||
| 541 | 1755 | pop(); |
| 542 | 1756 | |
| 543 | 1757 | // Add lighter highlights |
| 544 | - if (gamePhase === 'NIGHT') { | |
| 1758 | + if (gamePhase === 'NIGHT' || gamePhase === 'NIGHT_TO_DAWN') { | |
| 545 | 1759 | fill(50, 25, 10, 150); |
| 546 | 1760 | } else { |
| 547 | 1761 | fill(139, 90, 43, 180); |
@@ -574,7 +1788,7 @@ function drawSkyGradient() { | ||
| 574 | 1788 | |
| 575 | 1789 | // Knots |
| 576 | 1790 | noStroke(); |
| 577 | - if (gamePhase === 'NIGHT') { | |
| 1791 | + if (gamePhase === 'NIGHT' || gamePhase === 'NIGHT_TO_DAWN') { | |
| 578 | 1792 | fill(40, 20, 5); |
| 579 | 1793 | } else { |
| 580 | 1794 | fill(80, 40, 15); |
@@ -585,7 +1799,7 @@ function drawSkyGradient() { | ||
| 585 | 1799 | pop(); |
| 586 | 1800 | |
| 587 | 1801 | // Small twigs - properly attached to the rotated branch |
| 588 | - stroke(gamePhase === 'NIGHT' ? color(40, 20, 0) : color(101, 67, 33)); | |
| 1802 | + stroke(gamePhase === 'NIGHT' || gamePhase === 'NIGHT_TO_DAWN' ? color(40, 20, 0) : color(101, 67, 33)); | |
| 589 | 1803 | |
| 590 | 1804 | // Just add a couple simple twigs for visual interest |
| 591 | 1805 | strokeWeight(3); |
@@ -607,7 +1821,7 @@ function drawSkyGradient() { | ||
| 607 | 1821 | ellipse(2, 2, leaf.width, leaf.height); |
| 608 | 1822 | |
| 609 | 1823 | // Leaf body |
| 610 | - if (gamePhase === 'NIGHT') { | |
| 1824 | + if (gamePhase === 'NIGHT' || gamePhase === 'NIGHT_TO_DAWN') { | |
| 611 | 1825 | fill(20, 40, 20); |
| 612 | 1826 | } else { |
| 613 | 1827 | fill(34, 139, 34); |
@@ -625,8 +1839,7 @@ function drawSkyGradient() { | ||
| 625 | 1839 | } |
| 626 | 1840 | } |
| 627 | 1841 | |
| 628 | -function drawMoon() | |
| 629 | -{ | |
| 1842 | +function drawMoon() { | |
| 630 | 1843 | push(); |
| 631 | 1844 | noStroke(); |
| 632 | 1845 | |
@@ -678,7 +1891,11 @@ function drawMoon() | ||
| 678 | 1891 | } |
| 679 | 1892 | |
| 680 | 1893 | function updateResources() { |
| 681 | - webSilk = min(webSilk + silkRechargeRate, maxWebSilk); | |
| 1894 | + // PHASE 1 - Apply difficulty scaling to silk regen | |
| 1895 | + let silkPenalty = Math.floor((currentNight - 1) / 5) * 0.05; | |
| 1896 | + let adjustedRegenRate = silkRechargeRate * (1 - silkPenalty); | |
| 1897 | + | |
| 1898 | + webSilk = min(webSilk + adjustedRegenRate, maxWebSilk); | |
| 682 | 1899 | |
| 683 | 1900 | // Handle silk drain for both keyboard and touch |
| 684 | 1901 | if (isDeployingWeb && spider.isAirborne && (spacePressed || touchHolding) && webSilk > 0) { |
@@ -724,42 +1941,139 @@ function updateUI() { | ||
| 724 | 1941 | // Update control instructions based on device |
| 725 | 1942 | let isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); |
| 726 | 1943 | |
| 1944 | + // PHASE 3: Add upgrade-specific controls | |
| 1945 | + let controls = []; | |
| 727 | 1946 | if (isMobile) { |
| 728 | - document.getElementById('info').innerHTML = | |
| 729 | - 'Tap to jump • Hold mid-air for web • Double-tap spider to munch!<br>' + | |
| 730 | - 'Web Strands: <span id="strand-count">0</span><br>' + | |
| 731 | - 'Flies Caught: <span id="flies-caught">0</span> | Munched: <span id="flies-munched">0</span>'; | |
| 1947 | + controls.push('Tap to jump • Hold mid-air for web • Double-tap spider to munch!'); | |
| 732 | 1948 | } else { |
| 733 | - document.getElementById('info').innerHTML = | |
| 734 | - 'Click to jump • Space to spin web • Shift to munch!<br>' + | |
| 735 | - 'Web Strands: <span id="strand-count">0</span><br>' + | |
| 736 | - 'Flies Caught: <span id="flies-caught">0</span> | Munched: <span id="flies-munched">0</span>'; | |
| 1949 | + controls.push('Click to jump • Space to spin web • Shift to munch!'); | |
| 1950 | + } | |
| 1951 | + | |
| 1952 | + // Add upgrade controls | |
| 1953 | + if (upgrades.powerJump && upgrades.powerJump.level > 0) { | |
| 1954 | + controls.push('Hold click to charge jump!'); | |
| 1955 | + } | |
| 1956 | + if (upgrades.silkRecycle && upgrades.silkRecycle.level > 0) { | |
| 1957 | + controls.push('Press R to recycle web!'); | |
| 737 | 1958 | } |
| 738 | 1959 | |
| 739 | - document.getElementById('strand-count').textContent = webStrands.length; | |
| 1960 | + document.getElementById('info').innerHTML = | |
| 1961 | + controls.join('<br>') + '<br>' + | |
| 1962 | + 'Web Strands: <span id="strand-count">0</span><br>' + | |
| 1963 | + 'Flies Caught: <span id="flies-caught">0</span> | Munched: <span id="flies-munched">0</span><br>' + | |
| 1964 | + 'Total Score: <span id="total-score">0</span>'; | |
| 1965 | + | |
| 1966 | + // PHASE 1 UPDATES | |
| 1967 | + document.getElementById('strand-count').textContent = webStrands.filter(s => !s.broken).length; | |
| 740 | 1968 | document.getElementById('flies-caught').textContent = fliesCaught; |
| 741 | 1969 | document.getElementById('flies-munched').textContent = fliesMunched; |
| 742 | - document.getElementById('phase').textContent = gamePhase === 'TRANSITION' ? 'NIGHTFALL' : gamePhase; | |
| 1970 | + document.getElementById('total-score').textContent = totalFliesCaught; | |
| 1971 | + | |
| 1972 | + // Update phase display | |
| 1973 | + let phaseDisplay = gamePhase; | |
| 1974 | + if (gamePhase === 'DUSK_TO_NIGHT') phaseDisplay = 'NIGHTFALL'; | |
| 1975 | + else if (gamePhase === 'NIGHT_TO_DAWN') phaseDisplay = 'DAWN BREAKS'; | |
| 1976 | + else if (gamePhase === 'DAWN_TO_DAY') phaseDisplay = 'SUNRISE'; | |
| 1977 | + else if (gamePhase === 'DAY_TO_DUSK') phaseDisplay = 'SUNSET'; | |
| 1978 | + document.getElementById('phase').textContent = phaseDisplay; | |
| 1979 | + | |
| 1980 | + // Update night counter | |
| 1981 | + document.getElementById('night-counter').textContent = `Night ${currentNight}`; | |
| 743 | 1982 | |
| 1983 | + // Update timer based on phase | |
| 1984 | + let timerText = ''; | |
| 744 | 1985 | if (gamePhase === 'DUSK') { |
| 745 | 1986 | let timeLeft = Math.ceil((DUSK_DURATION - phaseTimer) / 60); |
| 746 | - document.getElementById('timer').textContent = `${timeLeft}s to prepare!`; | |
| 747 | - } else if (gamePhase === 'TRANSITION') { | |
| 748 | - document.getElementById('timer').textContent = 'Night approaches...'; | |
| 749 | - } else { | |
| 750 | - document.getElementById('timer').textContent = `${flies.length} flies active`; | |
| 1987 | + timerText = `${timeLeft}s to prepare!`; | |
| 1988 | + } else if (gamePhase === 'NIGHT') { | |
| 1989 | + let timeLeft = Math.ceil((NIGHT_DURATION - phaseTimer) / 60); | |
| 1990 | + // PHASE 2: Count different fly types | |
| 1991 | + let regularCount = flies.filter(f => f.type === 'regular').length; | |
| 1992 | + let goldenCount = flies.filter(f => f.type === 'golden').length; | |
| 1993 | + let mothCount = flies.filter(f => f.type === 'moth').length; | |
| 1994 | + let queenCount = flies.filter(f => f.type === 'queen').length; | |
| 1995 | + | |
| 1996 | + timerText = `${timeLeft}s • ${flies.length} flies`; | |
| 1997 | + | |
| 1998 | + // Show special fly counts if any | |
| 1999 | + if (goldenCount > 0 || mothCount > 0 || queenCount > 0) { | |
| 2000 | + let specialCounts = []; | |
| 2001 | + if (queenCount > 0) specialCounts.push(`${queenCount}👑`); | |
| 2002 | + if (goldenCount > 0) specialCounts.push(`${goldenCount}✨`); | |
| 2003 | + if (mothCount > 0) specialCounts.push(`${mothCount}🦋`); | |
| 2004 | + timerText += ` (${specialCounts.join(' ')})`; | |
| 2005 | + } | |
| 2006 | + } else if (gamePhase === 'DAWN') { | |
| 2007 | + let timeLeft = Math.ceil((DAWN_DURATION - phaseTimer) / 60); | |
| 2008 | + // PHASE 4: Show birds and exhaustion status | |
| 2009 | + let activeBirds = birds.filter(b => b.attacking).length; | |
| 2010 | + timerText = `${timeLeft}s • ${birds.length} birds`; | |
| 2011 | + if (activeBirds > 0) timerText += ` (${activeBirds} attacking!)`; | |
| 2012 | + if (isExhausted) timerText += ' EXHAUSTED!'; | |
| 2013 | + } else if (gamePhase === 'DAY') { | |
| 2014 | + timerText = 'Rest & repair'; | |
| 2015 | + } else if (gamePhase.includes('TO')) { | |
| 2016 | + timerText = '...'; | |
| 751 | 2017 | } |
| 2018 | + document.getElementById('timer').textContent = timerText; | |
| 752 | 2019 | |
| 753 | - let meterPercent = (webSilk / maxWebSilk) * 100; | |
| 754 | - document.getElementById('web-meter-fill').style.width = meterPercent + '%'; | |
| 2020 | + // Show difficulty indicators | |
| 2021 | + if (currentNight > 1) { | |
| 2022 | + let speedBonus = Math.floor((currentNight - 1) / 3) * 10; | |
| 2023 | + let silkPenalty = Math.floor((currentNight - 1) / 5) * 5; | |
| 2024 | + | |
| 2025 | + if (speedBonus > 0 || silkPenalty > 0) { | |
| 2026 | + let diffText = []; | |
| 2027 | + if (speedBonus > 0) diffText.push(`Flies +${speedBonus}% speed`); | |
| 2028 | + if (silkPenalty > 0) diffText.push(`Silk -${silkPenalty}% regen`); | |
| 2029 | + | |
| 2030 | + // Add a small difficulty indicator if needed | |
| 2031 | + if (gamePhase === 'DUSK' && phaseTimer < 180) { | |
| 2032 | + document.getElementById('timer').textContent += ` (${diffText.join(', ')})`; | |
| 2033 | + } | |
| 2034 | + } | |
| 2035 | + } | |
| 755 | 2036 | |
| 756 | - if (webSilk < 20) { | |
| 757 | - let flash = sin(frameCount * 0.2) * 0.5 + 0.5; | |
| 758 | - document.getElementById('web-meter-fill').style.background = | |
| 759 | - `linear-gradient(90deg, rgb(255, ${100 + flash * 100}, ${100 + flash * 100}), rgb(255, ${150 + flash * 50}, ${150 + flash * 50}))`; | |
| 2037 | + // PHASE 4: Update meter based on phase | |
| 2038 | + if (gamePhase === 'DAWN') { | |
| 2039 | + // Show stamina instead of silk during dawn | |
| 2040 | + document.getElementById('web-meter-label').textContent = 'STAMINA'; | |
| 2041 | + let staminaPercent = (jumpStamina / maxJumpStamina) * 100; | |
| 2042 | + document.getElementById('web-meter-fill').style.width = staminaPercent + '%'; | |
| 2043 | + | |
| 2044 | + // Color based on stamina level | |
| 2045 | + if (jumpStamina < jumpCost) { | |
| 2046 | + // Exhausted - red flash | |
| 2047 | + let flash = sin(frameCount * 0.3) * 0.5 + 0.5; | |
| 2048 | + document.getElementById('web-meter-fill').style.background = | |
| 2049 | + `linear-gradient(90deg, rgb(255, ${50 + flash * 50}, ${50 + flash * 50}), rgb(200, ${30 + flash * 30}, ${30 + flash * 30}))`; | |
| 2050 | + } else if (jumpStamina < maxJumpStamina * 0.3) { | |
| 2051 | + // Very tired - orange-red | |
| 2052 | + document.getElementById('web-meter-fill').style.background = | |
| 2053 | + 'linear-gradient(90deg, #FF6B35, #FF4444)'; | |
| 2054 | + } else if (jumpStamina < maxJumpStamina * 0.5) { | |
| 2055 | + // Tired - orange | |
| 2056 | + document.getElementById('web-meter-fill').style.background = | |
| 2057 | + 'linear-gradient(90deg, #FFA500, #FF8C00)'; | |
| 2058 | + } else { | |
| 2059 | + // Good stamina - yellow-orange | |
| 2060 | + document.getElementById('web-meter-fill').style.background = | |
| 2061 | + 'linear-gradient(90deg, #FFD700, #FFA500)'; | |
| 2062 | + } | |
| 760 | 2063 | } else { |
| 761 | - document.getElementById('web-meter-fill').style.background = | |
| 762 | - 'linear-gradient(90deg, #87CEEB, #E0F6FF)'; | |
| 2064 | + // Normal silk meter | |
| 2065 | + document.getElementById('web-meter-label').textContent = 'SILK'; | |
| 2066 | + let meterPercent = (webSilk / maxWebSilk) * 100; | |
| 2067 | + document.getElementById('web-meter-fill').style.width = meterPercent + '%'; | |
| 2068 | + | |
| 2069 | + if (webSilk < 20) { | |
| 2070 | + let flash = sin(frameCount * 0.2) * 0.5 + 0.5; | |
| 2071 | + document.getElementById('web-meter-fill').style.background = | |
| 2072 | + `linear-gradient(90deg, rgb(255, ${100 + flash * 100}, ${100 + flash * 100}), rgb(255, ${150 + flash * 50}, ${150 + flash * 50}))`; | |
| 2073 | + } else { | |
| 2074 | + document.getElementById('web-meter-fill').style.background = | |
| 2075 | + 'linear-gradient(90deg, #87CEEB, #E0F6FF)'; | |
| 2076 | + } | |
| 763 | 2077 | } |
| 764 | 2078 | } |
| 765 | 2079 | |
@@ -779,6 +2093,20 @@ function keyPressed() { | ||
| 779 | 2093 | spider.munch(); |
| 780 | 2094 | return false; |
| 781 | 2095 | } |
| 2096 | + // PHASE 3: Silk Recycle with R key | |
| 2097 | + if (key === 'r' || key === 'R') { | |
| 2098 | + if (upgrades.silkRecycle && upgrades.silkRecycle.level > 0) { | |
| 2099 | + recycleNearbyWeb(); | |
| 2100 | + } | |
| 2101 | + return false; | |
| 2102 | + } | |
| 2103 | + // PHASE 5: Stats panel with S key | |
| 2104 | + if (key === 's' || key === 'S') { | |
| 2105 | + if (gamePhase === 'DAY' || gamePhase === 'DUSK') { | |
| 2106 | + openStatsPanel(); | |
| 2107 | + } | |
| 2108 | + return false; | |
| 2109 | + } | |
| 782 | 2110 | } |
| 783 | 2111 | |
| 784 | 2112 | function keyReleased() { |
@@ -793,13 +2121,89 @@ function mousePressed() { | ||
| 793 | 2121 | // Only handle mouse on desktop (not touch devices) |
| 794 | 2122 | if (touches.length === 0) { |
| 795 | 2123 | if (!spider.isAirborne) { |
| 796 | - spider.jump(mouseX, mouseY); | |
| 2124 | + // PHASE 3: Power Jump - start charging if upgrade unlocked | |
| 2125 | + if (upgrades.powerJump && upgrades.powerJump.level > 0) { | |
| 2126 | + chargingJump = true; | |
| 2127 | + jumpChargeTime = 0; | |
| 2128 | + } else { | |
| 2129 | + spider.jump(mouseX, mouseY); | |
| 2130 | + } | |
| 797 | 2131 | } |
| 798 | 2132 | } |
| 799 | 2133 | } |
| 800 | 2134 | |
| 801 | 2135 | function mouseReleased() { |
| 802 | - // No longer needed for web deployment | |
| 2136 | + // PHASE 3: Power Jump - release charged jump | |
| 2137 | + if (chargingJump && !spider.isAirborne) { | |
| 2138 | + let chargeRatio = min(jumpChargeTime / maxJumpCharge, 1); | |
| 2139 | + let chargeMultiplier = 1 + chargeRatio; // 1x to 2x multiplier | |
| 2140 | + spider.jumpChargeVisual = 0; | |
| 2141 | + spider.jump(mouseX, mouseY, chargeMultiplier); | |
| 2142 | + | |
| 2143 | + // Create charge release particles | |
| 2144 | + if (chargeRatio > 0.5) { | |
| 2145 | + for (let i = 0; i < 10; i++) { | |
| 2146 | + let p = new Particle(spider.pos.x, spider.pos.y); | |
| 2147 | + p.color = color(255, 255, 100); | |
| 2148 | + p.vel = createVector(random(-3, 3), random(-1, 2)); | |
| 2149 | + p.size = 5; | |
| 2150 | + particles.push(p); | |
| 2151 | + } | |
| 2152 | + } | |
| 2153 | + } | |
| 2154 | + chargingJump = false; | |
| 2155 | + jumpChargeTime = 0; | |
| 2156 | +} | |
| 2157 | + | |
| 2158 | +// PHASE 3: Silk Recycle function | |
| 2159 | +function recycleNearbyWeb() { | |
| 2160 | + let recycled = false; | |
| 2161 | + | |
| 2162 | + for (let i = webStrands.length - 1; i >= 0; i--) { | |
| 2163 | + let strand = webStrands[i]; | |
| 2164 | + if (strand.broken) continue; | |
| 2165 | + | |
| 2166 | + // Check if spider is near any part of the strand | |
| 2167 | + let nearStrand = false; | |
| 2168 | + if (strand.path && strand.path.length > 0) { | |
| 2169 | + for (let point of strand.path) { | |
| 2170 | + if (dist(spider.pos.x, spider.pos.y, point.x, point.y) < 50) { | |
| 2171 | + nearStrand = true; | |
| 2172 | + break; | |
| 2173 | + } | |
| 2174 | + } | |
| 2175 | + } | |
| 2176 | + | |
| 2177 | + if (nearStrand) { | |
| 2178 | + // Recycle the strand | |
| 2179 | + webSilk = min(webSilk + 10, maxWebSilk); // Recover 50% of typical strand cost | |
| 2180 | + | |
| 2181 | + // Create recycling particles | |
| 2182 | + for (let j = 0; j < strand.path.length; j += 3) { | |
| 2183 | + let point = strand.path[j]; | |
| 2184 | + let p = new Particle(point.x, point.y); | |
| 2185 | + p.color = color(150, 255, 150); | |
| 2186 | + p.vel = createVector( | |
| 2187 | + (spider.pos.x - point.x) * 0.02, | |
| 2188 | + (spider.pos.y - point.y) * 0.02 | |
| 2189 | + ); | |
| 2190 | + p.size = 3; | |
| 2191 | + particles.push(p); | |
| 2192 | + } | |
| 2193 | + | |
| 2194 | + // Remove the strand | |
| 2195 | + webStrands.splice(i, 1); | |
| 2196 | + recycled = true; | |
| 2197 | + | |
| 2198 | + // Show notification | |
| 2199 | + notifications.push(new Notification("Web Recycled +10 Silk", color(150, 255, 150))); | |
| 2200 | + break; // Only recycle one strand at a time | |
| 2201 | + } | |
| 2202 | + } | |
| 2203 | + | |
| 2204 | + if (!recycled) { | |
| 2205 | + notifications.push(new Notification("No web nearby to recycle", color(255, 100, 100))); | |
| 2206 | + } | |
| 803 | 2207 | } |
| 804 | 2208 | |
| 805 | 2209 | function touchStarted() { |