import React, { useEffect, useRef, useState } from 'react'; import { Play, Trophy, Skull, RotateCcw } from 'lucide-react'; // --- Physics & Game Constants --- const CANVAS_WIDTH = 1200; const CANVAS_HEIGHT = 800; const GRAVITY = 1800; const APEX_GRAVITY_MULTI = 0.5; const JUMP_FORCE = -750; const MAX_FALL_SPEED = 900; const FAST_FALL_SPEED = 1400; const MAX_SPEED = 450; const ACCEL = 4000; const FRICTION = 3500; const AIR_ACCEL = 2500; const AIR_FRICTION = 800; const WALL_SLIDE_SPEED = 150; const WALL_JUMP_X = 500; const WALL_JUMP_Y = -650; const DASH_FORCE = 1200; const DASH_DURATION = 0.15; const DASH_COOLDOWN = 0.5; const COYOTE_TIME = 0.1; const JUMP_BUFFER = 0.12; // --- Sprites --- const SPRITE_URL = 'https://i.postimg.cc/0yjLcNTK/v1PXL_front_run_idle.png'; const SPRITE_DJ_URL = 'https://i.postimg.cc/gkNCVhTR/v1PXL_doublejump.png'; const SPRITE_FIRE_STAND_URL = 'https://i.postimg.cc/nc0y4mPB/v1PXL_fire_standing.png'; const SPRITE_FIRE_RUN_URL = 'https://i.postimg.cc/5NnhBzRz/v1PXL_fire.png'; const SPRITE_ATTACK_URL = 'https://i.postimg.cc/3JnsXGcj/v1PXL_front_run_attack.png'; const SPRITE_TRIP_URL = 'https://i.postimg.cc/3xNPnwsG/v1PXL_run_trip_fall.png'; const SPRITE_ENERGY_URL = 'https://i.postimg.cc/RVXk7tDw/v1PXL_energyball.png'; const SPRITE_JETPACK_URL = 'https://i.postimg.cc/MGqjTWQK/v1-jetpack.png'; export default function App() { const canvasRef = useRef(null); const [gameState, setGameState] = useState('MENU'); const [score, setScore] = useState(0); const [highScore, setHighScore] = useState(0); useEffect(() => { const handleGlobalEnter = (e) => { if (e.code === 'Enter' && gameState === 'GAMEOVER') { engineRef.current.needsReset = true; setScore(0); setGameState('PLAYING'); } }; window.addEventListener('keydown', handleGlobalEnter); return () => window.removeEventListener('keydown', handleGlobalEnter); }, [gameState]); // Massive state ref to completely avoid React render cycle bottlenecks during gameplay const engineRef = useRef({ needsReset: true, player: { x: 0, y: 0, w: 24, h: 40, vx: 0, vy: 0, isGrounded: false, touchingWall: 0, facingRight: true, coyoteTimer: 0, jumpBufferTimer: 0, canDash: true, dashTimer: 0, dashCooldownTimer: 0, hasDoubleJump: true, isDoubleJumping: false, djFrame: 0, djTick: 0, fireTimer: 0, tripTimer: 0, attackTimer: 0, shootAnimTimer: 0, attackBox: null, shootCooldown: 0, knockbackTimer: 0, fuel: 0, jetpackTimer: 0, hasAK: false, currentFrame: 0, frameTick: 0, renderFrame: 0, renderImageStr: 'spriteImage', }, camera: { x: 0, y: 0 }, mush: { y: 0, baseSpeed: 100, currentSpeed: 100 }, platforms: [], entities: [], enemies: [], traps: [], particles: [], trails: [], clouds: [], projectiles: [], keys: { left: false, right: false, up: false, down: false, dash: false, attack: false, shoot: false, jetpack: false }, prevKeys: { up: false, dash: false, attack: false, shoot: false, jetpack: false }, cheatBuffer: [], score: 0, highestY: 0, phase: 1, triggerPhase2: false, spawnedPhase2Cloud: false, transitionTimer: 0, // Asset Management spriteLoaded: false, spriteImage: null, spriteDJLoaded: false, spriteDJImage: null, spriteFireStandLoaded: false, spriteFireStandImage: null, spriteFireRunLoaded: false, spriteFireRunImage: null, spriteAttackLoaded: false, spriteAttackImage: null, spriteTripLoaded: false, spriteTripImage: null, spriteEnergyLoaded: false, spriteEnergyImage: null, spriteJetpackLoaded: false, spriteJetpackImage: null, }); // --- Initialization & Asset Loading --- useEffect(() => { const loadImage = (url, loadedKey, imageKey) => { const img = new Image(); img.crossOrigin = "Anonymous"; img.src = url; img.onload = () => { engineRef.current[loadedKey] = true; engineRef.current[imageKey] = img; }; }; loadImage(SPRITE_URL, 'spriteLoaded', 'spriteImage'); loadImage(SPRITE_DJ_URL, 'spriteDJLoaded', 'spriteDJImage'); loadImage(SPRITE_FIRE_STAND_URL, 'spriteFireStandLoaded', 'spriteFireStandImage'); loadImage(SPRITE_FIRE_RUN_URL, 'spriteFireRunLoaded', 'spriteFireRunImage'); loadImage(SPRITE_ATTACK_URL, 'spriteAttackLoaded', 'spriteAttackImage'); loadImage(SPRITE_TRIP_URL, 'spriteTripLoaded', 'spriteTripImage'); loadImage(SPRITE_ENERGY_URL, 'spriteEnergyLoaded', 'spriteEnergyImage'); loadImage(SPRITE_JETPACK_URL, 'spriteJetpackLoaded', 'spriteJetpackImage'); const handleKeyDown = (e) => { const { keys } = engineRef.current; if (e.code === 'KeyA' || e.code === 'ArrowLeft') keys.left = true; if (e.code === 'KeyD' || e.code === 'ArrowRight') keys.right = true; if (e.code === 'KeyW' || e.code === 'ArrowUp' || e.code === 'Space') keys.up = true; if (e.code === 'KeyF') keys.jetpack = true; if (e.code === 'KeyS' || e.code === 'ArrowDown') keys.down = true; if (e.code === 'ShiftLeft' || e.code === 'ShiftRight') keys.dash = true; // Hidden Encrypted Cheat Codes const cb = engineRef.current.cheatBuffer; if (e.key && e.key.length === 1) { cb.push(e.key.toLowerCase()); if (cb.length > 8) cb.shift(); const inputStr = cb.join(''); // "devbug" if (inputStr.includes(String.fromCharCode(100, 101, 118, 98, 117, 103))) { engineRef.current.player.fuel = 5; engineRef.current.player.hasAK = true; cb.length = 0; } // "survivor" if (inputStr.includes(String.fromCharCode(115, 117, 114, 118, 105, 118, 111, 114))) { const state = engineRef.current; state.triggerPhase2 = true; state.spawnedPhase2Cloud = true; state.phase = 'TRANSITION'; state.transitionTimer = 4.0; // Instantly spawn the cloud right above the player and clear the old platforms state.platforms = [ { x: -200, y: state.player.y - 400, w: CANVAS_WIDTH + 400, h: 600, type: 'PHASE_CLOUD', seed: Math.random(), isBurning: false } ]; // Clear entities and enemies for a clean cinematic transition state.enemies = []; state.traps = []; state.entities = []; state.projectiles = []; cb.length = 0; } } }; const handleKeyUp = (e) => { const { keys } = engineRef.current; if (e.code === 'KeyA' || e.code === 'ArrowLeft') keys.left = false; if (e.code === 'KeyD' || e.code === 'ArrowRight') keys.right = false; if (e.code === 'KeyW' || e.code === 'ArrowUp' || e.code === 'Space') keys.up = false; if (e.code === 'KeyF') keys.jetpack = false; if (e.code === 'KeyS' || e.code === 'ArrowDown') keys.down = false; if (e.code === 'ShiftLeft' || e.code === 'ShiftRight') keys.dash = false; }; const handleMouseDown = (e) => { if (e.button === 0) engineRef.current.keys.attack = true; if (e.button === 2) engineRef.current.keys.shoot = true; }; const handleMouseUp = (e) => { if (e.button === 0) engineRef.current.keys.attack = false; if (e.button === 2) engineRef.current.keys.shoot = false; }; const handleContextMenu = (e) => e.preventDefault(); window.addEventListener('keydown', handleKeyDown); window.addEventListener('keyup', handleKeyUp); window.addEventListener('mousedown', handleMouseDown); window.addEventListener('mouseup', handleMouseUp); window.addEventListener('contextmenu', handleContextMenu); return () => { window.removeEventListener('keydown', handleKeyDown); window.removeEventListener('keyup', handleKeyUp); window.removeEventListener('mousedown', handleMouseDown); window.removeEventListener('mouseup', handleMouseUp); window.removeEventListener('contextmenu', handleContextMenu); }; }, []); const rectIntersect = (a, b) => { if (!a || !b) return false; return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y; }; // --- Game Loop --- useEffect(() => { if (gameState !== 'PLAYING') return; const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); ctx.imageSmoothingEnabled = false; let animationFrameId; let lastTime = performance.now(); const resetGame = () => { const startY = 0; const state = engineRef.current; state.player = { ...state.player, x: CANVAS_WIDTH/2 - 12, y: startY - 100, vx: 0, vy: 0, isGrounded: false, touchingWall: 0, canDash: true, hasDoubleJump: true, isDoubleJumping: false, djFrame: 0, djTick: 0, fireTimer: 0, tripTimer: 0, attackTimer: 0, shootAnimTimer: 0, attackBox: null, shootCooldown: 0, knockbackTimer: 0, fuel: 0, jetpackTimer: 0, hasAK: false, renderFrame: 0, renderImageStr: 'spriteImage', }; state.camera = { x: 0, y: startY - CANVAS_HEIGHT / 2 }; state.mush = { y: startY + 600, baseSpeed: 90, currentSpeed: 90 }; state.platforms = [ { x: CANVAS_WIDTH/2 - 300, y: startY, w: 600, h: 40, type: 'ONEWAY', seed: 0.5, isBurning: false } ]; state.clouds = Array.from({length: 15}).map(() => ({ x: Math.random() * CANVAS_WIDTH * 2 - CANVAS_WIDTH, y: Math.random() * CANVAS_HEIGHT * 2 - CANVAS_HEIGHT, speed: 10 + Math.random() * 20, size: 0.5 + Math.random() })); state.entities = []; state.enemies = []; state.traps = []; state.particles = []; state.trails = []; state.projectiles = []; state.score = 0; state.highestY = startY; state.phase = 1; state.triggerPhase2 = false; state.spawnedPhase2Cloud = false; state.transitionTimer = 0; }; if (engineRef.current.needsReset) { resetGame(); engineRef.current.needsReset = false; } // --- Procedural Generation --- const generateSegment = () => { const state = engineRef.current; if ((state.triggerPhase2 || state.score >= 100000) && !state.spawnedPhase2Cloud) { state.spawnedPhase2Cloud = true; const cloudY = state.highestY - 800; state.platforms.push({ x: -200, y: cloudY, w: CANVAS_WIDTH + 400, h: 600, type: 'PHASE_CLOUD', seed: Math.random(), isBurning: false }); state.highestY = cloudY - 2000; // Stop normal spawning above the cloud } while (state.highestY > state.camera.y - CANVAS_HEIGHT - 400) { const gapY = 120 + Math.random() * 100; const topY = state.highestY - gapY; const difficulty = Math.min(1, state.score / 8000); const w = 150 + Math.random() * 200; const x = 50 + Math.random() * (CANVAS_WIDTH - w - 100); const isBurning = Math.random() < (0.02 + difficulty * 0.18); state.platforms.push({ x, y: topY, w, h: 30, type: 'ONEWAY', seed: Math.random(), isBurning }); let wallX = null; let wallW = null; if (Math.random() > 0.6) { wallW = 50 + Math.random() * 30; const wallH = 300 + Math.random() * 250; const gap = 80 + Math.random() * 60; wallX = (Math.random() > 0.5) ? x - wallW - gap : x + w + gap; if (wallX > 20 && wallX + wallW < CANVAS_WIDTH - 20) { state.platforms.push({ x: wallX, y: topY - wallH + 50, w: wallW, h: wallH, type: 'SOLID', seed: Math.random(), isBurning: false }); } else { wallX = null; } } if (Math.random() > 0.4) { const sideW = 80 + Math.random() * 120; const sideX = Math.random() * (CANVAS_WIDTH - sideW); const wallBounds = wallX !== null ? { min: wallX - 20, max: wallX + wallW + 20 } : null; const sideBounds = { min: sideX, max: sideX + sideW }; const collidesWithWall = wallBounds && (sideBounds.max > wallBounds.min && sideBounds.min < wallBounds.max); if (Math.abs(sideX - x) > 100 && !collidesWithWall) { const isCrumble = Math.random() > 0.7; const sideBurning = !isCrumble && Math.random() < (0.01 + difficulty * 0.1); state.platforms.push({ x: sideX, y: topY - (Math.random() * 60 - 30), w: sideW, h: 24, type: isCrumble ? 'CRUMBLE' : 'ONEWAY', crumbleTimer: isCrumble ? -1 : undefined, seed: Math.random(), isBurning: sideBurning }); } } if (!isBurning && Math.random() < 0.3) { state.enemies.push({ x: x + w/2 - 16, y: topY - 24, w: 32, h: 24, vx: 120 * (Math.random() > 0.5 ? 1 : -1), minX: x, maxX: x + w, dead: false }); } if (!isBurning && Math.random() < (0.01 + difficulty * 0.11)) { state.traps.push({ x: x + w/2 - 15, y: topY - 12, w: 30, h: 12, triggered: false }); } // Generate Gas Canisters (Reduced by 50% to 0.0625) if (!isBurning && Math.random() < 0.0625) { const gasX = x + 20 + Math.random() * (w - 70); state.entities.push({ type: 'GAS', x: gasX, y: topY - 40, w: 30, h: 40, active: true }); } // Generate AK-47 Rare Collectible if (!isBurning && Math.random() < 0.02) { state.entities.push({ type: 'AK47', x: x + w/2 - 20, y: topY - 30, w: 40, h: 15, active: true }); } if (Math.random() < 0.1) { state.entities.push({ type: 'UPDRAFT', x: x + w/2 - 40, y: topY - 500, w: 80, h: 500 }); } if (Math.random() < 0.05) { state.entities.push({ type: 'CRYSTAL', x: x + w/2 - 16, y: topY - 80, w: 32, h: 32, active: true }); } state.highestY = topY; } const cullY = state.camera.y + CANVAS_HEIGHT + 600; state.platforms = state.platforms.filter(p => p.y < cullY + (p.h||0)); state.entities = state.entities.filter(e => e.y < cullY); state.enemies = state.enemies.filter(e => e.y < cullY && !e.dead); state.traps = state.traps.filter(t => t.y < cullY); }; // --- Particle Systems --- const emitParticles = (x, y, color, count, speedMulti=1) => { for(let i=0; i { if (dt > 0.1) dt = 0.1; const state = engineRef.current; const p = state.player; // Handle Phase Transition Animation if (state.phase === 'TRANSITION') { state.transitionTimer -= dt; const cloud = state.platforms.find(pl => pl.type === 'PHASE_CLOUD'); if (!cloud) { state.phase = 2; return; } const targetY = cloud.y - p.h; // Top of the massive cloud // Disable horizontal movement during cinematic p.vx = 0; if (state.transitionTimer > 2.5) { // 1. Blast up rapidly through the thick cloud p.vy = -1500; p.y += p.vy * dt; p.currentFrame = 2; // Jump up frame p.isGrounded = false; } else { // 2. Prompt drop onto the cloud surface if (p.vy < 0) { p.vy = 0; // Zero out the rocket velocity at the apex to fall fast } p.vy += GRAVITY * 1.5 * dt; // Extra heavy gravity for a fast drop if (p.vy > FAST_FALL_SPEED * 1.5) p.vy = FAST_FALL_SPEED * 1.5; p.y += p.vy * dt; if (p.y >= targetY && p.vy > 0) { // Landed promptly on the cloud! if (!p.isGrounded) { // Emit landing impact particles just once emitParticles(p.x + p.w/2, targetY + p.h, '#ffffff', 40, 4); emitParticles(p.x + p.w/2, targetY + p.h, '#dceef7', 20, 2); } p.y = targetY; p.vy = 0; p.isGrounded = true; p.currentFrame = 0; // Relaxed Idle } else if (!p.isGrounded) { p.currentFrame = 6; // Falling down frame } } // Camera smoothly tracks the action state.camera.y += (p.y - CANVAS_HEIGHT * 0.6 - state.camera.y) * 8 * dt; if (state.transitionTimer <= 0) { state.phase = 2; } return; // Skip all normal game logic during transition } // Check Phase 2 Trigger if (state.phase === 1 && state.spawnedPhase2Cloud) { const cloud = state.platforms.find(pl => pl.type === 'PHASE_CLOUD'); // If player reaches the cloud's bounds, initiate transition cinematic if (cloud && p.y < cloud.y + cloud.h - 100) { state.phase = 'TRANSITION'; state.transitionTimer = 4.0; } } if (state.phase === 2) { // Placeholder for Phase 2 logic - locks player safely on the cloud for now p.vx = 0; p.vy = 0; return; } // 1. Status Effects let currentKeys = { ...state.keys }; if (p.knockbackTimer > 0) p.knockbackTimer -= dt; if (p.shootCooldown > 0) p.shootCooldown -= dt; if (p.shootAnimTimer > 0) p.shootAnimTimer -= dt; if (p.tripTimer > 0 && p.jetpackTimer <= 0) { p.tripTimer -= dt; if (p.tripTimer > 0.4) { p.vx = (p.facingRight ? 1 : -1) * 50; } else { p.vx = 0; } currentKeys = { left: false, right: false, up: false, down: false, dash: false, attack: false, shoot: false }; if (p.tripTimer <= 0) p.tripTimer = 0; } let speedMulti = 1.0; let jumpMulti = 1.0; if (p.fireTimer > 0) { p.fireTimer -= dt; if (p.fireTimer < 0) p.fireTimer = 0; speedMulti = 0.5; jumpMulti = 0.8; if (Math.random() < 0.2) emitParticles(p.x + Math.random()*p.w, p.y + p.h, '#ff5500', 1, 0.5); } // Melee Attack if (currentKeys.attack && !state.prevKeys.attack && p.attackTimer <= 0 && p.tripTimer <= 0 && !p.isDoubleJumping) { p.attackTimer = 0.3; } if (p.attackTimer > 0) { p.attackTimer -= dt; if (p.attackTimer < 0) p.attackTimer = 0; p.attackBox = { x: p.facingRight ? p.x + p.w : p.x - 50, y: p.y - 10, w: 50, h: p.h + 20 }; } else { p.attackBox = null; } // Ranged Projectile Shoot if (currentKeys.shoot && !state.prevKeys.shoot && p.shootCooldown <= 0 && p.tripTimer <= 0 && p.knockbackTimer <= 0) { p.shootCooldown = p.hasAK ? 0.15 : 0.5; // Faster shooting with AK p.shootAnimTimer = 0.3; state.projectiles.push({ x: p.facingRight ? p.x + p.w : p.x - 24, y: p.y + p.h/2 - 12, w: 24, h: 24, vx: (p.facingRight ? 1 : -1) * (p.hasAK ? 1400 : 800), // Faster projectile life: 1.5 }); } // 2. Timers & Jump Buffering (Including Jetpack Trigger) if (p.isGrounded) p.coyoteTimer = COYOTE_TIME; else p.coyoteTimer -= dt; // Rocketman Activation (Dedicated F Key) if (currentKeys.jetpack && !state.prevKeys.jetpack) { if (p.fuel >= 5 && p.jetpackTimer <= 0) { p.fuel = 0; p.jetpackTimer = 4.0; p.vy = -800; // Immediate kick emitParticles(p.x + p.w/2, p.y + p.h, '#ffaa00', 40, 5); } } // Jump Buffering (UP / SPACE) if (currentKeys.up && !state.prevKeys.up) { p.jumpBufferTimer = JUMP_BUFFER; } else { p.jumpBufferTimer -= dt; } if (p.dashCooldownTimer > 0) p.dashCooldownTimer -= dt; if (p.isGrounded) { p.canDash = true; p.touchingWall = 0; p.hasDoubleJump = true; p.isDoubleJumping = false; } // 3. Dash Execution let isDashing = false; if (currentKeys.dash && !state.prevKeys.dash && p.canDash && p.dashCooldownTimer <= 0 && p.tripTimer <= 0) { p.canDash = false; p.dashTimer = DASH_DURATION; p.dashCooldownTimer = DASH_COOLDOWN; emitParticles(p.x + p.w/2, p.y + p.h/2, '#00ffff', 15, 2); } if (p.dashTimer > 0) { isDashing = true; p.dashTimer -= dt; p.vy = 0; p.vx = (p.facingRight ? 1 : -1) * DASH_FORCE * speedMulti; if (Math.random() < 0.5) { state.trails.push({ x: p.x, y: p.y, frame: p.renderFrame, imgStr: p.renderImageStr, face: p.facingRight, life: 1.0 }); } } // 4. Normal Movement if (!isDashing && p.tripTimer <= 0 && p.knockbackTimer <= 0) { const accel = (p.isGrounded ? ACCEL : AIR_ACCEL) * speedMulti; const fric = (p.isGrounded ? FRICTION : AIR_FRICTION); const maxSpd = MAX_SPEED * speedMulti; if (currentKeys.left) { p.vx -= accel * dt; p.facingRight = false; } else if (currentKeys.right) { p.vx += accel * dt; p.facingRight = true; } else { if (p.vx > 0) p.vx = Math.max(0, p.vx - fric * dt); if (p.vx < 0) p.vx = Math.min(0, p.vx + fric * dt); } p.vx = Math.max(-maxSpd, Math.min(maxSpd, p.vx)); } else if (p.knockbackTimer > 0) { const fric = AIR_FRICTION; if (p.vx > 0) p.vx = Math.max(0, p.vx - fric * dt); if (p.vx < 0) p.vx = Math.min(0, p.vx + fric * dt); } // 5. Gravity, Jetpack & Jumping if (p.jetpackTimer > 0) { p.jetpackTimer -= dt; p.vy = -800; // Constant rocket thrust p.isDoubleJumping = false; p.tripTimer = 0; p.touchingWall = 0; if (Math.random() < 0.4) emitParticles(p.x + p.w/2, p.y + p.h + 20, '#ff5500', 3, 4); if (Math.random() < 0.2) emitParticles(p.x + p.w/2, p.y + p.h + 20, '#ffffff', 2, 2); if (p.jetpackTimer <= 0) p.jetpackTimer = 0; } else if (!isDashing && p.tripTimer <= 0) { let currentGravity = GRAVITY; if (Math.abs(p.vy) < 150 && currentKeys.up) { currentGravity *= APEX_GRAVITY_MULTI; } if (currentKeys.down) { p.vy += 4000 * dt; if (p.vy > FAST_FALL_SPEED) p.vy = FAST_FALL_SPEED; } else { p.vy += currentGravity * dt; if (p.vy > MAX_FALL_SPEED) p.vy = MAX_FALL_SPEED; } if (!currentKeys.up && p.vy < -200 && p.jetpackTimer <= 0) { p.vy = -200; } if (p.touchingWall !== 0 && p.vy > 0 && !p.isGrounded) { if ( (p.touchingWall === -1 && currentKeys.left) || (p.touchingWall === 1 && currentKeys.right) ) { p.vy = WALL_SLIDE_SPEED; emitParticles(p.touchingWall === -1 ? p.x : p.x+p.w, p.y + p.h, '#ffffff', 1, 0.2); } } if (p.jumpBufferTimer > 0 && p.knockbackTimer <= 0) { const jumpPwr = JUMP_FORCE * jumpMulti; if (p.coyoteTimer > 0) { p.vy = jumpPwr; p.jumpBufferTimer = 0; p.coyoteTimer = 0; p.isDoubleJumping = false; emitParticles(p.x + p.w/2, p.y + p.h, '#80b918', 8); } else if (p.touchingWall !== 0 && !p.isGrounded) { p.vy = WALL_JUMP_Y * jumpMulti; p.vx = -p.touchingWall * WALL_JUMP_X * jumpMulti; p.jumpBufferTimer = 0; p.facingRight = p.touchingWall === -1; p.canDash = true; p.hasDoubleJump = true; p.isDoubleJumping = false; emitParticles(p.touchingWall === -1 ? p.x : p.x+p.w, p.y + p.h/2, '#bdc3c7', 10); } else if (p.hasDoubleJump) { p.vy = jumpPwr; p.jumpBufferTimer = 0; p.hasDoubleJump = false; p.isDoubleJumping = true; p.djFrame = 0; p.djTick = 0; emitParticles(p.x + p.w/2, p.y + p.h, '#00ffcc', 12); } } } else if (p.tripTimer > 0) { p.vy += GRAVITY * dt; if (p.vy > MAX_FALL_SPEED) p.vy = MAX_FALL_SPEED; } // 6. Collision Detection (Platforms) let nextX = p.x + p.vx * dt; let nextY = p.y + p.vy * dt; p.isGrounded = false; p.touchingWall = 0; for (let plat of state.platforms) { if (plat.type === 'ONEWAY' || plat.type === 'CRUMBLE') continue; if (nextX < plat.x + plat.w && nextX + p.w > plat.x && p.y < plat.y + plat.h && p.y + p.h > plat.y) { if (p.vx > 0) { nextX = plat.x - p.w; p.vx = 0; p.touchingWall = 1; } else if (p.vx < 0) { nextX = plat.x + plat.w; p.vx = 0; p.touchingWall = -1; } } } p.x = nextX; if (p.x < -p.w) p.x = CANVAS_WIDTH; if (p.x > CANVAS_WIDTH) p.x = -p.w; for (let plat of state.platforms) { if (plat.type === 'CRUMBLE' && plat.crumbleTimer === 0) continue; if (p.x < plat.x + plat.w && p.x + p.w > plat.x && nextY < plat.y + plat.h && nextY + p.h > plat.y) { if (p.vy > 0) { if (plat.type === 'ONEWAY' || plat.type === 'CRUMBLE') { if (p.y + p.h <= plat.y + 10) { nextY = plat.y - p.h; p.vy = 0; p.isGrounded = true; if (plat.type === 'CRUMBLE' && plat.crumbleTimer === -1) plat.crumbleTimer = 0.5; if (plat.isBurning) p.fireTimer = 3.0; } } else { nextY = plat.y - p.h; p.vy = 0; p.isGrounded = true; } } else if (p.vy < 0 && plat.type === 'SOLID') { nextY = plat.y + plat.h; p.vy = 0; } } } p.y = nextY; state.platforms.forEach(plat => { if (plat.type === 'CRUMBLE' && plat.crumbleTimer > 0) { plat.crumbleTimer -= dt; if (plat.crumbleTimer <= 0) { plat.crumbleTimer = 0; emitParticles(plat.x + plat.w/2, plat.y, '#5a3a2b', 20, 2); } } }); // 7. Enemies & Traps Intersects state.traps.forEach(t => { if (!t.triggered && rectIntersect(p, t)) { t.triggered = true; if (p.jetpackTimer <= 0) p.tripTimer = 1.0; } }); state.enemies.forEach(e => { if (e.dead) return; e.x += e.vx * dt; if (e.x < e.minX) { e.x = e.minX; e.vx *= -1; } if (e.x + e.w > e.maxX) { e.x = e.maxX - e.w; e.vx *= -1; } if (p.attackTimer > 0 && rectIntersect(p.attackBox, e)) { e.dead = true; state.score += 150; emitParticles(e.x + e.w/2, e.y + e.h/2, '#00ff66', 20, 3); } else if (rectIntersect(p, e)) { if (p.jetpackTimer > 0) { e.dead = true; state.score += 150; emitParticles(e.x + e.w/2, e.y + e.h/2, '#ffaa00', 30, 4); } else if (p.vy > 0 && p.y + p.h - p.vy * dt < e.y + 10) { e.dead = true; p.vy = JUMP_FORCE * 0.7; state.score += 150; emitParticles(e.x + e.w/2, e.y + e.h/2, '#00ff66', 20, 3); } else if (p.tripTimer <= 0 && p.knockbackTimer <= 0) { const dir = p.x < e.x ? -1 : 1; p.vx = dir * 750; p.vy = -650; p.knockbackTimer = 0.4; emitParticles(p.x + p.w/2, p.y + p.h/2, '#ff0033', 15, 3); } } state.projectiles.forEach(proj => { if (proj.life > 0 && rectIntersect(proj, e)) { e.dead = true; proj.life = 0; state.score += 200; emitParticles(e.x + e.w/2, e.y + e.h/2, '#ff66b2', 25, 4); } }); }); state.projectiles.forEach(proj => { proj.x += proj.vx * dt; proj.life -= dt; if (Math.random() < 0.6) { state.particles.push({ x: proj.x + proj.w/2 + (Math.random()-0.5)*16, y: proj.y + proj.h/2 + (Math.random()-0.5)*16, vx: -proj.vx * 0.1, vy: (Math.random()-0.5)*20, life: 0.6, decay: 0.05, size: Math.random()*3 + 2, color: Math.random() > 0.5 ? '#ffffff' : '#ff66b2' }); } }); state.projectiles = state.projectiles.filter(p => p.life > 0); for (let ent of state.entities) { if (ent.type === 'GAS' && ent.active && rectIntersect(p, ent)) { ent.active = false; state.score += 100; if (p.fuel < 5) p.fuel += 1; emitParticles(ent.x + 15, ent.y + 20, '#f1c40f', 20, 3); } if (p.x < ent.x + ent.w && p.x + p.w > ent.x && p.y < ent.y + ent.h && p.y + p.h > ent.y) { if (ent.type === 'UPDRAFT') { p.vy -= 4500 * dt; if (p.vy < -1200) p.vy = -1200; p.canDash = true; p.hasDoubleJump = true; p.isDoubleJumping = false; if (Math.random()<0.3) emitParticles(p.x+p.w/2, p.y+p.h, '#ffd700', 1); } if (ent.type === 'CRYSTAL' && ent.active) { ent.active = false; state.score += 500; state.mush.y += 800; emitParticles(ent.x+16, ent.y+16, '#00ffcc', 30, 3); } if (ent.type === 'AK47' && ent.active) { ent.active = false; state.score += 300; p.hasAK = true; emitParticles(ent.x+20, ent.y+7, '#ffffff', 20, 2); } } } // 8. Camera const targetCamY = p.y - CANVAS_HEIGHT * 0.66; state.camera.x = 0; const camLerpY = targetCamY > state.camera.y ? 8 : 4; state.camera.y += (targetCamY - state.camera.y) * camLerpY * dt; // 9. Jade Mass const distToMush = state.mush.y - p.y; if (distToMush > 1200) state.mush.currentSpeed = state.mush.baseSpeed * 2.5; else if (distToMush < 400) state.mush.currentSpeed = state.mush.baseSpeed * 0.6; else state.mush.currentSpeed = state.mush.baseSpeed * (1 + (state.score/5000)); state.mush.y -= state.mush.currentSpeed * dt; state.clouds.forEach(c => { c.x += c.speed * dt; if (c.x > CANVAS_WIDTH + 200) c.x = -400; }); const heightScore = Math.floor(Math.max(0, -p.y)); if (heightScore > state.score) state.score = heightScore; generateSegment(); if (p.y > state.mush.y + 50) { setGameState('GAMEOVER'); setHighScore(prev => Math.max(prev, state.score)); } state.prevKeys = { ...currentKeys }; engineRef.current.keys.attack = false; engineRef.current.keys.shoot = false; engineRef.current.keys.jetpack = false; // Requires distinct press state.particles.forEach(pt => { pt.x += pt.vx * dt; pt.y += pt.vy * dt; pt.life -= pt.decay; }); state.particles = state.particles.filter(pt => pt.life > 0); state.trails.forEach(tr => { tr.life -= dt * 3; }); state.trails = state.trails.filter(tr => tr.life > 0); // Sprite Frame Counters & Resolution let activeImgStr = 'spriteImage'; if (p.jetpackTimer > 0 && state.spriteJetpackLoaded) activeImgStr = 'spriteJetpackImage'; else if (p.tripTimer > 0 && state.spriteTripLoaded) activeImgStr = 'spriteTripImage'; else if ((p.attackTimer > 0 || p.shootAnimTimer > 0) && state.spriteAttackLoaded) activeImgStr = 'spriteAttackImage'; else if (p.fireTimer > 0) { if (Math.abs(p.vx) > 10 && p.isGrounded && state.spriteFireRunLoaded) activeImgStr = 'spriteFireRunImage'; else if (state.spriteFireStandLoaded) activeImgStr = 'spriteFireStandImage'; } else if (p.isDoubleJumping && state.spriteDJLoaded) activeImgStr = 'spriteDJImage'; else if (state.spriteLoaded) activeImgStr = 'spriteImage'; p.renderImageStr = activeImgStr; const activeImg = state[activeImgStr]; if (activeImg) { const maxFrames = Math.max(1, Math.floor(activeImg.width / activeImg.height)); p.frameTick += dt; if (p.frameTick > 0.08) { p.currentFrame++; p.frameTick = 0; } if (p.tripTimer > 0) { p.renderFrame = Math.min(maxFrames - 1, Math.floor((1 - p.tripTimer / 1.0) * maxFrames)); } else if (p.attackTimer > 0) { p.renderFrame = Math.min(maxFrames - 1, Math.floor((1 - p.attackTimer / 0.3) * maxFrames)); } else if (p.shootAnimTimer > 0) { p.renderFrame = Math.min(maxFrames - 1, Math.floor((1 - p.shootAnimTimer / 0.3) * maxFrames)); } else if (p.isDoubleJumping) { p.djTick += dt; if (p.djTick > 0.05) { p.djTick = 0; if (p.djFrame < maxFrames - 1) p.djFrame++; else if (p.vy > 0) p.isDoubleJumping = false; } p.renderFrame = Math.min(maxFrames - 1, p.djFrame); } else if (activeImgStr === 'spriteFireStandImage' || activeImgStr === 'spriteFireRunImage') { p.renderFrame = p.currentFrame % maxFrames; } else if (Math.abs(p.vx) > 50 && p.isGrounded) { p.renderFrame = p.currentFrame % maxFrames; } else if (!p.isGrounded) { p.renderFrame = p.vy < 0 ? Math.min(2, maxFrames - 1) : Math.min(6, maxFrames - 1); } else { p.renderFrame = 0; } } }; // --- Rendering --- const draw = () => { const state = engineRef.current; const { camera, player: p, mush } = state; const skyGrad = ctx.createLinearGradient(0, 0, 0, CANVAS_HEIGHT); skyGrad.addColorStop(0, '#3a8ccf'); skyGrad.addColorStop(0.5, '#73b2e3'); skyGrad.addColorStop(1, '#aedff7'); ctx.fillStyle = skyGrad; ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); ctx.save(); const mntY = -(camera.y * 0.1) % 1500; ctx.fillStyle = '#6ab0b5'; ctx.beginPath(); ctx.moveTo(0, mntY + 800); ctx.lineTo(200, mntY + 400); ctx.lineTo(400, mntY + 600); ctx.lineTo(700, mntY + 300); ctx.lineTo(1000, mntY + 550); ctx.lineTo(1200, mntY + 450); ctx.lineTo(1200, mntY + 1500); ctx.lineTo(0, mntY + 1500); ctx.fill(); const nearY = -(camera.y * 0.25) % 1500; ctx.fillStyle = '#4a9088'; ctx.beginPath(); ctx.moveTo(0, nearY + 900); ctx.lineTo(150, nearY + 500); ctx.lineTo(350, nearY + 750); ctx.lineTo(600, nearY + 450); ctx.lineTo(850, nearY + 800); ctx.lineTo(1150, nearY + 550); ctx.lineTo(1200, nearY + 600); ctx.lineTo(1200, nearY + 1500); ctx.lineTo(0, nearY + 1500); ctx.fill(); ctx.fillStyle = 'rgba(255, 255, 255, 0.4)'; state.clouds.forEach(c => { const cY = c.y - (camera.y * 0.15); ctx.beginPath(); ctx.arc(c.x, cY, 40 * c.size, 0, Math.PI*2); ctx.arc(c.x + 30*c.size, cY - 20*c.size, 50 * c.size, 0, Math.PI*2); ctx.arc(c.x + 70*c.size, cY, 45 * c.size, 0, Math.PI*2); ctx.fill(); }); ctx.translate(-camera.x, -camera.y); state.entities.forEach(ent => { if (ent.type === 'GAS' && ent.active) { ctx.fillStyle = '#e67e22'; ctx.fillRect(ent.x, ent.y, ent.w, ent.h); ctx.fillStyle = '#d35400'; ctx.fillRect(ent.x + ent.w - 8, ent.y, 8, ent.h); ctx.fillStyle = '#bdc3c7'; ctx.fillRect(ent.x + 8, ent.y - 6, 14, 6); ctx.fillStyle = 'rgba(255,255,255,0.2)'; ctx.fillRect(ent.x + 2, ent.y, 4, ent.h); ctx.fillStyle = '#ffffff'; ctx.font = 'bold 10px monospace'; ctx.textAlign = 'center'; ctx.fillText('GAS', ent.x + ent.w/2, ent.y + 12); ctx.font = 'bold 8px monospace'; ctx.fillText('for', ent.x + ent.w/2, ent.y + 22); ctx.font = 'bold 10px monospace'; ctx.fillText('TXN', ent.x + ent.w/2, ent.y + 32); } else if (ent.type === 'AK47' && ent.active) { ctx.fillStyle = '#7f8c8d'; ctx.fillRect(ent.x, ent.y, 40, 6); ctx.fillStyle = '#d35400'; ctx.fillRect(ent.x - 10, ent.y, 12, 10); ctx.fillRect(ent.x + 15, ent.y + 6, 6, 12); ctx.fillStyle = '#e74c3c'; ctx.fillRect(ent.x + 25, ent.y + 6, 10, 4); } else if (ent.type === 'UPDRAFT') { const gradient = ctx.createLinearGradient(0, ent.y, 0, ent.y + ent.h); gradient.addColorStop(0, 'rgba(255, 215, 0, 0)'); gradient.addColorStop(0.5, 'rgba(255, 215, 0, 0.2)'); gradient.addColorStop(1, 'rgba(255, 215, 0, 0)'); ctx.fillStyle = gradient; ctx.fillRect(ent.x, ent.y, ent.w, ent.h); ctx.fillStyle = '#ffd700'; const time = performance.now() / 1000; for(let i=0; i<4; i++) { const lineY = ent.y + ent.h - ((time * 300 + i*150) % ent.h); ctx.globalAlpha = 0.6 * Math.sin(lineY / ent.h * Math.PI); ctx.beginPath(); const sway = Math.sin(lineY*0.05 + time)*15; ctx.arc(ent.x + ent.w/2 + sway, lineY, 3, 0, Math.PI*2); ctx.fill(); } ctx.globalAlpha = 1.0; } else if (ent.type === 'CRYSTAL' && ent.active) { ctx.fillStyle = '#ff66b2'; ctx.shadowColor = '#ff66b2'; ctx.shadowBlur = 20; ctx.beginPath(); const floatY = ent.y + Math.sin(performance.now()/300)*5; ctx.arc(ent.x + 16, floatY + 16, 12, 0, Math.PI*2); ctx.fill(); ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(ent.x + 12, floatY + 12, 4, 0, Math.PI*2); ctx.fill(); ctx.shadowBlur = 0; } }); state.platforms.forEach(plat => { if (plat.type === 'CRUMBLE' && plat.crumbleTimer === 0) return; let rx = plat.x; let ry = plat.y; if (plat.type === 'PHASE_CLOUD') { // Draw 16-bit premium pixel-art cloud (half-screen thick) const pSize = 16; const cols = Math.ceil(plat.w / pSize); const rows = Math.ceil(plat.h / pSize); for (let c = 0; c < cols; c++) { // Generate organic, seeded bumpy top const seed = c * 13.5; const offset = Math.floor((Math.sin(seed*0.1) + Math.sin(seed*0.3)*0.5) * 4) + 4; const startRow = offset; const bx = rx + c * pSize; for (let r = startRow; r < rows; r++) { const by = ry + r * pSize; const depth = r - startRow; // Color gradient based on depth into the cloud let color = '#ffffff'; if (depth > 1) color = '#dceef7'; if (depth > 4) color = '#b2d5e8'; if (depth > 9) color = '#88b6d1'; if (depth > 19) color = '#5b8fae'; // 16-bit Dithering / Blending pattern if (depth === 2 && (c + r) % 2 === 0) color = '#ffffff'; if (depth === 5 && (c + r) % 2 === 0) color = '#dceef7'; if (depth === 10 && (c + r) % 2 === 0) color = '#b2d5e8'; if (depth === 20 && (c + r) % 2 === 0) color = '#88b6d1'; ctx.fillStyle = color; ctx.fillRect(bx, by, pSize, pSize); } } return; } if (plat.type === 'CRUMBLE' && plat.crumbleTimer > 0 && plat.crumbleTimer < 0.5) { rx += (Math.random() - 0.5) * 6; ry += (Math.random() - 0.5) * 6; } if (plat.type === 'SOLID') { ctx.fillStyle = '#495a5c'; ctx.fillRect(rx, ry, plat.w, plat.h); ctx.fillStyle = '#647b7d'; ctx.fillRect(rx + 4, ry, plat.w - 8, plat.h); ctx.fillStyle = '#3a693f'; const numMoss = Math.floor(plat.seed * 8) + 3; for(let i=0; i 4) { ctx.fillRect(rx + i, ry - 4, 6, 4); } } // Deep Red & Black Charcoals if (plat.isBurning) { const fTime = performance.now() * 0.002; ctx.fillStyle = '#0a0202'; ctx.fillRect(rx, ry - 8, plat.w, 10); for(let i=0; i { if (t.triggered) return; ctx.fillStyle = '#7f8c8d'; ctx.fillRect(t.x, t.y - 4, 6, 16); ctx.fillRect(t.x + t.w - 6, t.y - 4, 6, 16); ctx.fillStyle = '#ff0033'; ctx.fillRect(t.x + 1, t.y - 4, 4, 4); ctx.fillRect(t.x + t.w - 5, t.y - 4, 4, 4); ctx.strokeStyle = 'rgba(255, 50, 50, 0.9)'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(t.x + 3, t.y + 4); ctx.lineTo(t.x + t.w - 3, t.y + 4); ctx.stroke(); ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(t.x + 3, t.y + 4); ctx.lineTo(t.x + t.w - 3, t.y + 4); ctx.stroke(); }); const eTime = performance.now(); state.enemies.forEach(e => { if (e.dead) return; const cx = e.x + e.w/2; const cy = e.y + e.h/2 + 4; const r = e.w/2; ctx.save(); ctx.shadowColor = '#00ff66'; ctx.shadowBlur = 15; ctx.fillStyle = 'rgba(0, 255, 102, 0.8)'; ctx.beginPath(); for(let i=0; i0 ? 4 : -8), cy - 4, 4, 4); ctx.restore(); }); let visualW = 48; let visualH = 48; let offsetX = -12; let offsetY = -8; // Scale character by 75% when jetpack is active if (p.jetpackTimer > 0) { visualW *= 1.75; visualH *= 1.75; // Adjust offsets to keep character grounded at feet and centered offsetX = - (visualW - p.w) / 2; offsetY = p.h - visualH + 8; } state.trails.forEach(tr => { ctx.save(); ctx.globalAlpha = tr.life * 0.5; if (!tr.face) { ctx.translate(tr.x + p.w, tr.y); ctx.scale(-1, 1); } else { ctx.translate(tr.x, tr.y); } ctx.filter = 'drop-shadow(0 0 8px #00ffff) hue-rotate(90deg) brightness(2)'; const trImg = state[tr.imgStr]; if (trImg) { const maxFrames = Math.max(1, Math.floor(trImg.width / trImg.height)); const fw = trImg.width / maxFrames; ctx.drawImage(trImg, tr.frame * fw, 0, fw, trImg.height, offsetX, offsetY, visualW, visualH); } ctx.restore(); }); const activeImage = state[p.renderImageStr]; if (activeImage) { const maxFrames = Math.max(1, Math.floor(activeImage.width / activeImage.height)); const fw = activeImage.width / maxFrames; ctx.save(); if (!p.facingRight) { ctx.translate(p.x + p.w, p.y); ctx.scale(-1, 1); } else { ctx.translate(p.x, p.y); } // Jetpack Rendering logic integrated inside the scaled activeImage if using v1-jetpack sprite ctx.drawImage(activeImage, p.renderFrame * fw, 0, fw, activeImage.height, offsetX, offsetY, visualW, visualH); ctx.restore(); } else { ctx.fillStyle = '#ff0077'; ctx.fillRect(p.x, p.y, p.w, p.h); } // Attack Swoosh Cone (Melee Only) if (p.attackTimer > 0) { const progress = 1 - (p.attackTimer / 0.3); ctx.save(); ctx.translate(p.facingRight ? p.x + p.w : p.x, p.y + p.h / 2); if (!p.facingRight) ctx.scale(-1, 1); ctx.beginPath(); ctx.moveTo(0, 0); ctx.arc(0, 0, 60 + progress * 20, -Math.PI/2.5, Math.PI/2.5); ctx.closePath(); const grad = ctx.createRadialGradient(0, 0, 10, 0, 0, 80); grad.addColorStop(0, `rgba(255, 255, 255, ${0.8 * (1 - progress)})`); grad.addColorStop(1, `rgba(0, 255, 255, 0)`); ctx.fillStyle = grad; ctx.fill(); ctx.beginPath(); ctx.arc(0, 0, 50 + progress * 25, -Math.PI/3, Math.PI/3); ctx.strokeStyle = `rgba(255, 255, 255, ${1 - progress})`; ctx.lineWidth = 3; ctx.stroke(); ctx.restore(); } // Projectiles rendering (Custom Procedural Energy Ball + Dynamic Lightning) state.projectiles.forEach(proj => { ctx.save(); ctx.translate(proj.x + proj.w/2, proj.y + proj.h/2); if (proj.vx < 0) ctx.scale(-1, 1); const pTime = performance.now(); // 1. Pink/Magenta Pulsing Plasma Aura const pulse = Math.sin(pTime * 0.01 + proj.x * 0.01) * 4; const radGrad = ctx.createRadialGradient(0, 0, 0, 0, 0, 24 + pulse); radGrad.addColorStop(0, '#ffffff'); radGrad.addColorStop(0.3, '#ff66b2'); radGrad.addColorStop(0.7, '#cc0066'); radGrad.addColorStop(1, 'rgba(204, 0, 102, 0)'); ctx.fillStyle = radGrad; ctx.beginPath(); ctx.arc(0, 0, 24 + pulse, 0, Math.PI * 2); ctx.fill(); // 2. Dynamic White Lightning Arcs ctx.shadowColor = '#ffffff'; ctx.shadowBlur = 10 + Math.random() * 10; ctx.strokeStyle = '#ffffff'; ctx.lineWidth = 1.5 + Math.random(); ctx.beginPath(); for(let i=0; i<6; i++) { const angle = (Math.PI * 2 / 6) * i + (Math.random() * Math.PI/4); const dist1 = 8 + Math.random() * 8; const dist2 = 20 + Math.random() * 18; ctx.moveTo(Math.cos(angle)*dist1, Math.sin(angle)*dist1); const midAngle = angle + (Math.random() - 0.5); const midDist = dist1 + (dist2 - dist1)/2; ctx.lineTo(Math.cos(midAngle)*midDist, Math.sin(midAngle)*midDist); ctx.lineTo(Math.cos(angle)*dist2, Math.sin(angle)*dist2); } ctx.stroke(); ctx.shadowBlur = 0; // 3. Dense Inner Star Core ctx.fillStyle = '#ffffff'; ctx.beginPath(); ctx.arc(0, 0, 6, 0, Math.PI * 2); ctx.fill(); ctx.restore(); }); state.particles.forEach(pt => { ctx.fillStyle = pt.color; ctx.globalAlpha = pt.life; ctx.fillRect(pt.x, pt.y, pt.size, pt.size); ctx.globalAlpha = 1.0; }); const mushTop = mush.y; ctx.globalCompositeOperation = 'hard-light'; const glowGrad = ctx.createLinearGradient(0, mushTop - 300, 0, mushTop); glowGrad.addColorStop(0, 'rgba(0, 255, 102, 0)'); glowGrad.addColorStop(1, 'rgba(0, 255, 102, 0.7)'); ctx.fillStyle = glowGrad; ctx.fillRect(camera.x, mushTop - 300, CANVAS_WIDTH, 300); ctx.globalCompositeOperation = 'source-over'; ctx.fillStyle = '#0a2114'; ctx.fillRect(camera.x, mushTop, CANVAS_WIDTH, camera.y + CANVAS_HEIGHT - mushTop + 500); const t2 = performance.now(); ctx.fillStyle = '#00ff66'; ctx.beginPath(); ctx.moveTo(camera.x, mushTop + 20); for(let x=0; x<=CANVAS_WIDTH; x+=40) { const wave1 = Math.sin((x + camera.x)*0.01 + t2*0.003) * 15; const wave2 = Math.cos((x + camera.x)*0.02 + t2*0.007) * 8; ctx.lineTo(camera.x + x, mushTop + wave1 + wave2); } ctx.lineTo(camera.x + CANVAS_WIDTH, mushTop + 100); ctx.lineTo(camera.x, mushTop + 100); ctx.fill(); for (let i = 0; i < 40; i++) { const artX = camera.x + (Math.random() * CANVAS_WIDTH); const artY = mushTop + (Math.random() * 60) - 20; const w = Math.random() * 50 + 10; const h = Math.random() * 8 + 2; ctx.fillStyle = Math.random() > 0.6 ? '#ffffff' : '#003311'; ctx.globalAlpha = Math.random() * 0.8; ctx.fillRect(artX, artY, w, h); } ctx.globalAlpha = 1.0; ctx.restore(); // UI OVERLAYS - Transition / Phase 2 Text if (state.phase === 'TRANSITION') { ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); } if (state.phase === 2) { ctx.fillStyle = 'rgba(255, 255, 255, 1)'; ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); ctx.fillStyle = 'black'; ctx.font = 'bold 64px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(`PHASE 2 IMMINENT`, CANVAS_WIDTH/2, CANVAS_HEIGHT/2); ctx.font = '24px sans-serif'; ctx.fillText(`Prepare yourself.`, CANVAS_WIDTH/2, CANVAS_HEIGHT/2 + 50); return; // Skip normal UI } // UI OVERLAYS - Text Information ctx.fillStyle = 'white'; ctx.font = 'bold 28px sans-serif'; ctx.textAlign = 'left'; ctx.shadowColor = 'black'; ctx.shadowBlur = 6; ctx.fillText(`HEIGHT: ${state.score}m`, 20, 50); if (p.canDash && p.dashCooldownTimer <= 0) { ctx.fillStyle = '#00ffff'; ctx.fillText(`⯅ DASH READY (SHIFT)`, 20, 90); } else { ctx.fillStyle = '#dddddd'; ctx.fillText(`⯅ DASH RECHARGING`, 20, 90); ctx.fillStyle = 'rgba(0,0,0,0.5)'; ctx.fillRect(20, 100, 150, 6); ctx.fillStyle = '#00ffff'; ctx.fillRect(20, 100, 150 * (1 - p.dashCooldownTimer/DASH_COOLDOWN), 6); } if (p.fireTimer > 0) { ctx.fillStyle = '#ff5500'; ctx.fillText(`🔥 BURNING! SPEED REDUCED`, 20, 130); } ctx.shadowBlur = 0; // --- Fuel UI Metric --- const fuelUiY = CANVAS_HEIGHT - 70; const fuelUiX = 30; ctx.save(); let renderFuelX = fuelUiX; let renderFuelY = fuelUiY; // Shake effect when full if (p.fuel === 5 && p.jetpackTimer <= 0) { renderFuelX += (Math.random() - 0.5) * 4; renderFuelY += (Math.random() - 0.5) * 4; ctx.shadowColor = '#00ff66'; ctx.shadowBlur = 15; } ctx.fillStyle = 'rgba(5, 10, 15, 0.8)'; ctx.strokeStyle = p.fuel === 5 ? '#00ff66' : 'rgba(255, 255, 255, 0.15)'; ctx.lineWidth = 1; ctx.fillRect(renderFuelX, renderFuelY, 240, 46); ctx.strokeRect(renderFuelX, renderFuelY, 240, 46); ctx.shadowBlur = 0; // Reset shadow for text ctx.fillStyle = '#a3c2c2'; ctx.font = 'bold 11px monospace'; ctx.textAlign = 'left'; ctx.fillText('ROCKETMAN MODE', renderFuelX + 16, renderFuelY + 18); const segmentW = 38; const segmentH = 8; const gap = 4; const startX = renderFuelX + 16; const startY = renderFuelY + 28; if (p.jetpackTimer > 0) { ctx.fillStyle = '#00ffff'; ctx.shadowColor = '#00ffff'; ctx.shadowBlur = 10; ctx.fillRect(startX, startY, 206 * (p.jetpackTimer / 4.0), segmentH); } else { for(let i=0; i<5; i++) { ctx.fillStyle = i < p.fuel ? (p.fuel === 5 ? '#00ff66' : '#00ffff') : 'rgba(255,255,255,0.1)'; if (i < p.fuel) { ctx.shadowColor = p.fuel === 5 ? '#00ff66' : '#00ffff'; ctx.shadowBlur = 8; } else { ctx.shadowBlur = 0; } ctx.fillRect(startX + i * (segmentW + gap), startY, segmentW, segmentH); } if (p.fuel === 5) { const pulse = (Math.sin(performance.now() * 0.01) + 1) / 2; ctx.fillStyle = `rgba(0, 255, 102, ${pulse})`; ctx.font = 'bold 12px sans-serif'; ctx.fillText('READY (F)', renderFuelX + 110, renderFuelY + 8); } } ctx.restore(); // --- AK-47 UI --- if (p.hasAK) { ctx.fillStyle = 'rgba(5, 10, 15, 0.8)'; ctx.fillRect(CANVAS_WIDTH - 150, CANVAS_HEIGHT - 70, 120, 50); ctx.fillStyle = '#7f8c8d'; ctx.fillRect(CANVAS_WIDTH - 130, CANVAS_HEIGHT - 50, 60, 8); ctx.fillStyle = '#8e44ad'; ctx.fillRect(CANVAS_WIDTH - 90, CANVAS_HEIGHT - 50, 10, 18); ctx.fillStyle = '#d35400'; ctx.fillRect(CANVAS_WIDTH - 140, CANVAS_HEIGHT - 50, 15, 14); ctx.fillStyle = '#ffffff'; ctx.font = 'bold 12px monospace'; ctx.fillText('AK-47 EQUIPPED', CANVAS_WIDTH - 135, CANVAS_HEIGHT - 28); } }; const loop = (timestamp) => { let dt = (timestamp - lastTime) / 1000; lastTime = timestamp; update(dt); draw(); if (gameState === 'PLAYING') { animationFrameId = requestAnimationFrame(loop); } }; animationFrameId = requestAnimationFrame(loop); return () => cancelAnimationFrame(animationFrameId); }, [gameState]); return (
e.preventDefault()}>
{gameState === 'MENU' && (

V1 ABSTRACT ESCAPE

Ascend The Overgrowth

Move: Arrows / WASD

Jump / Double Jump: UP / W / SPACE

Rocketman Jetpack: F KEY

Mid-Air Dash: SHIFT

Attack Monsters: Left Click

Shoot Projectile: Right Click

Beware of Tripwires and Burning Platforms.
Collect 5 GAS canisters to equip your Rocket Jetpack!

)} {gameState === 'GAMEOVER' && (

Assimilated

Height Reached {score}m
RECORD {highScore}m

Press ENTER to restart

)}
); }