hi

Pasted Sep 21, 2026, 11:49 PM1 file1,780 lines

Scan to open on your phone

Point a camera at the QR code - it opens this exact paste, no app needed.

https://codepastes.com/zzgtdga7wkx0jsyj
hi
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>Zombie Survival FPS - Hardcore Open World</title>
    <style>
        * {
            box-sizing: border-box;
            user-select: none;
        }

        body {
            margin: 0;
            overflow: hidden;
            background-color: #000;
            font-family: 'Courier New', Courier, monospace;
            color: #fff;
        }

        #ui-layer {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            display: flex;
            flex-direction: column;
            justify-content: space-between;
            z-index: 5;
        }

        /* HUD Top */
        .hud-top {
            display: flex;
            justify-content: space-between;
            align-items: flex-start;
            padding: 20px;
            font-size: 20px;
            color: #00ff66;
            text-shadow: 0 0 6px #00ff66;
        }

        .hud-stats {
            display: flex;
            flex-direction: column;
            gap: 6px;
        }

        .coins-display {
            color: #ffd700;
            text-shadow: 0 0 8px #ffaa00;
            font-weight: bold;
            font-size: 24px;
        }

        .wood-display {
            color: #e3a857;
            text-shadow: 0 0 8px #8b4513;
            font-weight: bold;
            font-size: 22px;
        }

        /* Crosshair */
        #crosshair {
            position: absolute;
            top: 50%;
            left: 50%;
            width: 22px;
            height: 22px;
            transform: translate(-50%, -50%);
            pointer-events: none;
        }
        #crosshair::before, #crosshair::after {
            content: '';
            position: absolute;
            background: #00ff66;
            box-shadow: 0 0 4px #00ff66;
        }
        #crosshair::before {
            top: 10px; left: 0; width: 22px; height: 2px;
        }
        #crosshair::after {
            left: 10px; top: 0; width: 2px; height: 22px;
        }

        #action-prompt {
            position: absolute;
            top: 56%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: #00ffff;
            font-size: 18px;
            font-weight: bold;
            text-shadow: 0 0 8px #00ffff;
            display: none;
            pointer-events: none;
            background: rgba(0,0,0,0.75);
            padding: 8px 16px;
            border-radius: 6px;
            border: 1px solid #00ffff;
        }

        /* HUD Bottom */
        .hud-bottom {
            display: flex;
            justify-content: space-between;
            align-items: flex-end;
            padding: 20px;
            color: #fff;
        }

        .bar-container {
            width: 260px;
            height: 24px;
            background: #111;
            border: 2px solid #555;
            border-radius: 4px;
            overflow: hidden;
            margin-bottom: 5px;
        }

        #health-bar {
            width: 100%;
            height: 100%;
            background: #ff1111;
            box-shadow: 0 0 10px #ff1111;
            transition: width 0.15s ease-out;
        }

        #ammo-display {
            font-size: 32px;
            color: #ffff00;
            text-shadow: 0 0 6px #ffff00;
        }

        #weapon-name {
            font-size: 18px;
            color: #aaa;
            text-transform: uppercase;
            letter-spacing: 1px;
        }

        /* Rear Radar Sensor */
        #radar-container {
            position: absolute;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            width: 140px;
            height: 140px;
            background: rgba(0, 20, 0, 0.85);
            border: 2px solid #00ff66;
            border-radius: 50%;
            display: none;
            box-shadow: 0 0 12px rgba(0, 255, 102, 0.4);
            pointer-events: none;
        }

        #radar-canvas {
            width: 100%;
            height: 100%;
            border-radius: 50%;
        }

        #rear-warning {
            position: absolute;
            top: 168px;
            left: 50%;
            transform: translateX(-50%);
            color: #ff0000;
            font-size: 16px;
            font-weight: bold;
            display: none;
            text-shadow: 0 0 8px #ff0000;
            animation: warningBlink 0.35s infinite alternate;
        }

        @keyframes warningBlink {
            from { opacity: 0.2; }
            to { opacity: 1; }
        }

        /* Overlay Screens */
        #blocker {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.88);
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            color: #fff;
            z-index: 10;
            pointer-events: auto;
            cursor: pointer;
        }

        #shop-overlay {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(5, 10, 18, 0.96);
            display: none;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            z-index: 20;
            color: #fff;
            pointer-events: auto;
            padding: 20px;
            overflow-y: auto;
        }

        .shop-header {
            font-size: 36px;
            color: #ffd700;
            text-shadow: 0 0 12px #ff9900;
            margin-bottom: 4px;
            font-weight: bold;
            letter-spacing: 2px;
        }

        .shop-grid {
            display: grid;
            grid-template-columns: repeat(3, 240px);
            gap: 14px;
            max-width: 800px;
            margin-bottom: 18px;
        }

        .shop-card {
            background: #0f1724;
            border: 2px solid #23354d;
            border-radius: 8px;
            padding: 14px;
            display: flex;
            flex-direction: column;
            align-items: center;
            text-align: center;
            transition: transform 0.1s, border-color 0.2s;
        }

        .shop-card:hover {
            border-color: #ffd700;
            transform: translateY(-2px);
        }

        .shop-card h3 {
            margin: 2px 0 6px 0;
            color: #00ccff;
            font-size: 17px;
        }

        .shop-card p {
            font-size: 12px;
            color: #9aa8bc;
            margin: 0 0 10px 0;
            height: 32px;
            line-height: 1.3;
        }

        .shop-btn {
            background: #ffd700;
            color: #000;
            border: none;
            padding: 9px 14px;
            font-weight: bold;
            font-size: 13px;
            border-radius: 4px;
            cursor: pointer;
            width: 100%;
            transition: background 0.15s;
        }

        .shop-btn:hover { background: #ffffff; }
        .shop-btn:disabled { background: #333d4d; color: #6d7d93; cursor: not-allowed; }

        .shop-footer {
            display: flex;
            gap: 20px;
            align-items: center;
        }

        .close-shop-btn {
            padding: 12px 28px;
            background: #cc1111;
            color: white;
            border: none;
            font-size: 16px;
            cursor: pointer;
            border-radius: 6px;
            font-weight: bold;
        }
        .close-shop-btn:hover { background: #ff2222; }

        .reset-btn {
            padding: 12px 20px;
            background: #331111;
            color: #ff7777;
            border: 1px solid #882222;
            font-size: 14px;
            cursor: pointer;
            border-radius: 6px;
            font-weight: bold;
        }
        .reset-btn:hover { background: #aa0000; color: #fff; }

        h1 { margin: 0; font-size: 46px; color: #ff1111; text-transform: uppercase; letter-spacing: 4px; text-shadow: 0 0 14px #ff0000; }
        p { font-size: 16px; color: #ccc; }

        #damage-overlay {
            position: absolute;
            top: 0; left: 0; right: 0; bottom: 0;
            box-shadow: inset 0 0 0 0 rgba(255,0,0,0);
            pointer-events: none;
            transition: box-shadow 0.1s;
        }
        .damage-active {
            box-shadow: inset 0 0 120px 60px rgba(255,0,0,0.7) !important;
        }

        #ammo-message {
            position: absolute;
            top: 62%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: #ff2222;
            font-size: 26px;
            font-weight: bold;
            display: none;
            text-shadow: 2px 2px 0 #000;
        }

        .controls-hint {
            position: absolute;
            bottom: 80px;
            left: 20px;
            font-size: 13px;
            color: #bbb;
            pointer-events: none;
            line-height: 1.6;
            background: rgba(0,0,0,0.65);
            padding: 10px 14px;
            border-radius: 6px;
            border: 1px solid #333;
        }

        #toast-msg {
            position: absolute;
            top: 12%;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0, 255, 200, 0.95);
            color: #000;
            padding: 10px 24px;
            font-weight: bold;
            border-radius: 20px;
            display: none;
            z-index: 30;
            box-shadow: 0 0 20px rgba(0,255,200,0.6);
            font-size: 15px;
        }
    </style>
    <!-- Load Three.js library -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
</head>
<body>

    <!-- Game HUD -->
    <div id="ui-layer">
        <div class="hud-top">
            <div class="hud-stats">
                <div id="score">SCORE: 0</div>
                <div id="coins" class="coins-display">COINS: $0</div>
                <div id="wood" class="wood-display">🪵 WOOD: 0</div>
            </div>
            <div id="wave">WAVE: 1</div>
        </div>

        <div id="radar-container">
            <canvas id="radar-canvas" width="140" height="140"></canvas>
            <div id="rear-warning">⚠️ BEHIND YOU!</div>
        </div>

        <div id="action-prompt">PRESS [E] TO INTERACT</div>
        <div id="ammo-message">RELOAD! (R)</div>
        <div id="crosshair"></div>

        <div class="controls-hint">
            <b>[E]</b> Chop Trees / Loot Crates<br>
            <b>[C]</b> Build Wall (15 Wood)<br>
            <b>[F]</b> Flashlight | <b>[B]</b> Shop<br>
            <b>[1-4]</b> Gun Swap | <b>[R]</b> Reload
        </div>

        <div class="hud-bottom">
            <div>
                <div class="bar-container">
                    <div id="health-bar"></div>
                </div>
                <div style="color:#ff2222; font-weight:bold;">HEALTH <span id="health-num">100/100</span></div>
            </div>
            <div style="text-align: right;">
                <div id="weapon-name">PISTOL</div>
                <div id="ammo-display">12 / 12</div>
                <div style="color:#ffff00; font-weight:bold;">AMMO</div>
            </div>
        </div>
    </div>

    <div id="damage-overlay"></div>
    <div id="toast-msg">ACCOUNT RESET SUCCESSFUL</div>

    <!-- Start / Pause Overlay -->
    <div id="blocker">
        <div id="instructions" style="text-align: center;">
            <h1 id="title-text">HARDCORE ZOMBIE SECTOR</h1>
            <p style="margin-top: 15px; font-weight: bold; color: #00ff66;">CLICK TO PLAY</p>
            <p style="font-size: 14px; color: #aaa; margin-top:15px; line-height: 1.6;">
                WASD to Move &bull; MOUSE to Aim &bull; CLICK to Fire<br>
                <b>E</b> Chop Trees / Loot Buildings &bull; <b>C</b> Build Barricades &bull; <b>F</b> Mega Flashlight<br>
                <b>B</b> Shop Upgrades (Health up to 500, Radar, Guns)
            </p>
        </div>
    </div>

    <!-- Arsenal Shop Overlay -->
    <div id="shop-overlay">
        <div class="shop-header">ARSENAL & SURVIVAL UPGRADES</div>
        <div style="color: #ffd700; margin-bottom: 14px; font-size: 20px; font-weight: bold;" id="shop-coin-count">COINS: $5000 | WOOD: 0</div>

        <div class="shop-grid">
            <!-- Shotgun -->
            <div class="shop-card">
                <h3>Shotgun</h3>
                <p>Devastating heavy close-range burst firing 8 pellets.</p>
                <button class="shop-btn" id="buy-shotgun" onclick="buyItem('shotgun')">Buy $150</button>
            </div>
            <!-- Assault Rifle -->
            <div class="shop-card">
                <h3>Assault Rifle</h3>
                <p>High-speed automatic rifle with 30-round magazine.</p>
                <button class="shop-btn" id="buy-rifle" onclick="buyItem('rifle')">Buy $350</button>
            </div>
            <!-- Plasma Cannon -->
            <div class="shop-card">
                <h3>Plasma Cannon</h3>
                <p>Extreme power laser energy cannon for giant bosses.</p>
                <button class="shop-btn" id="buy-plasma" onclick="buyItem('plasma')">Buy $750</button>
            </div>
            <!-- Mega Flashlight -->
            <div class="shop-card">
                <h3>Ultra Flashlight</h3>
                <p>High-powered mega beam searchlight (Press F).</p>
                <button class="shop-btn" id="buy-flashlight" onclick="buyItem('flashlight')">Buy $50</button>
            </div>
            <!-- Rear Radar Sensor -->
            <div class="shop-card">
                <h3>Rear Sensor Radar</h3>
                <p>360° Radar & warning alerts for zombies behind you.</p>
                <button class="shop-btn" id="buy-rear-sensor" onclick="buyItem('rear_sensor')">Buy $125</button>
            </div>
            <!-- Health Boost up to 500 -->
            <div class="shop-card">
                <h3>Max Health Boost</h3>
                <p>+50 Max HP Upgrade (Up to 500 Max HP!). Heals full.</p>
                <button class="shop-btn" id="buy-health" onclick="buyItem('health')">Buy $100</button>
            </div>
            <!-- Speed Boost -->
            <div class="shop-card">
                <h3>Sprint Speed</h3>
                <p>Permanent +25% player sprint movement speed.</p>
                <button class="shop-btn" id="buy-speed" onclick="buyItem('speed')">Buy $120</button>
            </div>
            <!-- Damage Amp -->
            <div class="shop-card">
                <h3>Damage Amp</h3>
                <p>Permanent +50% extra firepower for all guns.</p>
                <button class="shop-btn" id="buy-damage" onclick="buyItem('damage')">Buy $200</button>
            </div>
            <!-- Wood Pack -->
            <div class="shop-card">
                <h3>Wood Crate (+30)</h3>
                <p>Instant lumber supply for building defensive walls.</p>
                <button class="shop-btn" id="buy-wood" onclick="buyItem('wood_pack')">Buy $50</button>
            </div>
        </div>

        <div class="shop-footer">
            <button class="close-shop-btn" onclick="toggleShop(false)">RESUME GAME (B)</button>
            <button class="reset-btn" onclick="resetAccountData()">⚠️ RESET ACCOUNT</button>
        </div>
    </div>

    <script>
        // Synthesized Web Audio API Sound Generator
        const audioCtx = new (window.AudioContext || window.webkitAudioContext)();

        function playSound(type) {
            if (audioCtx.state === 'suspended') audioCtx.resume();
            const osc = audioCtx.createOscillator();
            const gainNode = audioCtx.createGain();
            osc.connect(gainNode);
            gainNode.connect(audioCtx.destination);

            const now = audioCtx.currentTime;

            if (type === 'shoot_pistol') {
                osc.type = 'sawtooth'; osc.frequency.setValueAtTime(650, now);
                osc.frequency.exponentialRampToValueAtTime(80, now + 0.12);
                gainNode.gain.setValueAtTime(0.3, now); gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.12);
                osc.start(now); osc.stop(now + 0.12);
            } else if (type === 'shoot_shotgun') {
                osc.type = 'square'; osc.frequency.setValueAtTime(320, now);
                osc.frequency.exponentialRampToValueAtTime(40, now + 0.25);
                gainNode.gain.setValueAtTime(0.5, now); gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.25);
                osc.start(now); osc.stop(now + 0.25);
            } else if (type === 'shoot_rifle') {
                osc.type = 'sawtooth'; osc.frequency.setValueAtTime(850, now);
                osc.frequency.exponentialRampToValueAtTime(110, now + 0.08);
                gainNode.gain.setValueAtTime(0.25, now); gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.08);
                osc.start(now); osc.stop(now + 0.08);
            } else if (type === 'shoot_plasma') {
                osc.type = 'sine'; osc.frequency.setValueAtTime(1300, now);
                osc.frequency.exponentialRampToValueAtTime(180, now + 0.2);
                gainNode.gain.setValueAtTime(0.4, now); gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.2);
                osc.start(now); osc.stop(now + 0.2);
            } else if (type === 'chop') {
                osc.type = 'square'; osc.frequency.setValueAtTime(200, now);
                osc.frequency.exponentialRampToValueAtTime(60, now + 0.14);
                gainNode.gain.setValueAtTime(0.35, now); gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.14);
                osc.start(now); osc.stop(now + 0.14);
            } else if (type === 'build') {
                osc.type = 'triangle'; osc.frequency.setValueAtTime(220, now);
                osc.frequency.setValueAtTime(330, now + 0.1);
                gainNode.gain.setValueAtTime(0.3, now); gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.2);
                osc.start(now); osc.stop(now + 0.2);
            } else if (type === 'empty') {
                osc.type = 'square'; osc.frequency.setValueAtTime(800, now);
                gainNode.gain.setValueAtTime(0.1, now); gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.05);
                osc.start(now); osc.stop(now + 0.05);
            } else if (type === 'reload') {
                osc.type = 'sine'; osc.frequency.setValueAtTime(300, now);
                osc.frequency.linearRampToValueAtTime(600, now + 0.25);
                gainNode.gain.setValueAtTime(0.2, now); gainNode.gain.linearRampToValueAtTime(0, now + 0.35);
                osc.start(now); osc.stop(now + 0.35);
            } else if (type === 'hit') {
                osc.type = 'square'; osc.frequency.setValueAtTime(140, now);
                osc.frequency.exponentialRampToValueAtTime(30, now + 0.2);
                gainNode.gain.setValueAtTime(0.35, now); gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.2);
                osc.start(now); osc.stop(now + 0.2);
            } else if (type === 'zombie_die') {
                osc.type = 'sawtooth'; osc.frequency.setValueAtTime(190, now);
                osc.frequency.linearRampToValueAtTime(30, now + 0.35);
                gainNode.gain.setValueAtTime(0.3, now); gainNode.gain.linearRampToValueAtTime(0, now + 0.35);
                osc.start(now); osc.stop(now + 0.35);
            } else if (type === 'coin') {
                osc.type = 'sine'; osc.frequency.setValueAtTime(987.77, now);
                osc.frequency.setValueAtTime(1318.51, now + 0.08);
                gainNode.gain.setValueAtTime(0.2, now); gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.25);
                osc.start(now); osc.stop(now + 0.25);
            } else if (type === 'buy') {
                osc.type = 'triangle'; osc.frequency.setValueAtTime(523.25, now);
                osc.frequency.setValueAtTime(659.25, now + 0.08);
                osc.frequency.setValueAtTime(783.99, now + 0.16);
                gainNode.gain.setValueAtTime(0.25, now); gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.3);
                osc.start(now); osc.stop(now + 0.3);
            } else if (type === 'loot') {
                osc.type = 'sine'; osc.frequency.setValueAtTime(440, now);
                osc.frequency.setValueAtTime(880, now + 0.12);
                gainNode.gain.setValueAtTime(0.3, now); gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.3);
                osc.start(now); osc.stop(now + 0.3);
            }
        }

        let camera, scene, renderer;
        let raycaster;
        let moveForward = false, moveBackward = false, moveLeft = false, moveRight = false, canJump = false, isRunning = false;
        let prevTime = performance.now();
        const velocity = new THREE.Vector3();
        const direction = new THREE.Vector3();

        let isGameActive = false;
        let isShopOpen = false;
        let score = 0;
        let coins = 5000; // Updated starting balance to $5,000
        let wood = 20;
        let wave = 1;
        let health = 100;
        let maxHealth = 100;
        let speedMultiplier = 1.0;
        let damageMultiplier = 1.0;

        // Flashlight
        let flashlightSpot;
        let hasFlashlight = false;
        let flashlightOn = false;

        // Rear Sensor / Radar
        let hasRearSensor = false;

        // Weapon Configuration
        const WEAPONS = {
            pistol: { name: "Pistol", damage: 65, fireRate: 200, maxAmmo: 12, pelletCount: 1, spread: 0.02, sound: 'shoot_pistol', unlocked: true },
            shotgun: { name: "Shotgun", damage: 45, fireRate: 650, maxAmmo: 6, pelletCount: 8, spread: 0.12, sound: 'shoot_shotgun', unlocked: false },
            rifle: { name: "Assault Rifle", damage: 55, fireRate: 90, maxAmmo: 30, pelletCount: 1, spread: 0.035, sound: 'shoot_rifle', unlocked: false },
            plasma: { name: "Plasma Cannon", damage: 320, fireRate: 400, maxAmmo: 8, pelletCount: 1, spread: 0.01, sound: 'shoot_plasma', unlocked: false }
        };
        let currentWeaponKey = 'pistol';
        let ammoMap = { pistol: 12, shotgun: 6, rifle: 30, plasma: 8 };
        let isShooting = false;
        let lastShotTime = 0;
        let isReloading = false;

        // Weapon Meshes
        let weaponGroup;
        let muzzleFlashMesh;
        let gunMeshes = {};

        // World Collections
        let zombies = [];
        let trees = [];
        let barricades = [];
        let particles = [];
        let buildings = [];
        let buildingBounds = []; // Bounding boxes for Safe Zones
        let lootCrates = [];
        let targetedTree = null;
        let targetedCrate = null;

        let lastSpawnTime = 0;
        let spawnRate = 1200; // Slightly reduced spawn rate for easier gameplay
        let baseZombieSpeed = 16; // Reduced zombie base speed
        let playerBaseSpeed = 44;

        // Lighting & Theme elements
        let hemiLight, dirLight;

        init();
        loadAccountData();
        animate();

        function init() {
            camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1500);
            camera.position.y = 10;

            scene = new THREE.Scene();
            scene.background = new THREE.Color(0x0a0d14);
            scene.fog = new THREE.FogExp2(0x0a0d14, 0.0018);

            // Lights
            hemiLight = new THREE.HemisphereLight(0x555566, 0x111122, 0.5);
            scene.add(hemiLight);

            dirLight = new THREE.DirectionalLight(0xff7733, 0.4);
            dirLight.position.set(60, 100, 40);
            scene.add(dirLight);

            // Mega Flashlight attached to Camera
            flashlightSpot = new THREE.SpotLight(0xffffff, 0, 450, Math.PI / 3.5, 0.35, 1);
            flashlightSpot.position.set(0, 0, 0);
            flashlightSpot.target.position.set(0, 0, -1);
            camera.add(flashlightSpot);
            camera.add(flashlightSpot.target);

            // Control Listeners
            const canvas = document.body;
            const blocker = document.getElementById('blocker');

            blocker.addEventListener('click', function () {
                if (!isShopOpen) canvas.requestPointerLock();
            });

            document.addEventListener('pointerlockchange', function () {
                if (document.pointerLockElement === canvas) {
                    isGameActive = true;
                    blocker.style.display = 'none';
                    if (health <= 0) resetGame();
                } else {
                    if (!isShopOpen) {
                        isGameActive = false;
                        blocker.style.display = 'flex';
                    }
                }
            });

            document.addEventListener('keydown', onKeyDown);
            document.addEventListener('keyup', onKeyUp);
            document.addEventListener('mousedown', onMouseDown);
            document.addEventListener('mouseup', onMouseUp);
            document.addEventListener('mousemove', onMouseMove);

            raycaster = new THREE.Raycaster(new THREE.Vector3(), new THREE.Vector3(0, 0, -1), 0, 150);

            // Expanded Terrain Floor (7,500 x 7,500 Grid)
            const gridSize = 512;
            const gridCanvas = document.createElement('canvas');
            gridCanvas.width = gridSize; gridCanvas.height = gridSize;
            const ctx = gridCanvas.getContext('2d');
            ctx.fillStyle = '#111822'; ctx.fillRect(0, 0, gridSize, gridSize);
            ctx.strokeStyle = '#1e2c3e'; ctx.lineWidth = 4; ctx.strokeRect(0, 0, gridSize, gridSize);

            const gridTexture = new THREE.CanvasTexture(gridCanvas);
            gridTexture.wrapS = THREE.RepeatWrapping; gridTexture.wrapT = THREE.RepeatWrapping;
            gridTexture.repeat.set(250, 250);

            const floorGeometry = new THREE.PlaneGeometry(7500, 7500);
            const floorMaterial = new THREE.MeshStandardMaterial({ map: gridTexture, roughness: 0.85 });
            const floor = new THREE.Mesh(floorGeometry, floorMaterial);
            floor.rotation.x = - Math.PI / 2;
            scene.add(floor);

            // World Objects Generation (Scaled for 7,500x7,500 world)
        generateTrees(250);
        generateBuildings(25);
        createWeapons();

        // Spawn initial zombies immediately so player isn't alone
        for (let i = 0; i < 8; i++) {
            spawnZombie();
        }

        renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setPixelRatio(window.devicePixelRatio);
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.toneMapping = THREE.ACESFilmicToneMapping;
            document.body.appendChild(renderer.domElement);

            window.addEventListener('resize', onWindowResize);
            updateThemeForWave();
        }

        function generateTrees(count) {
            const trunkGeo = new THREE.CylinderGeometry(1.0, 1.5, 14, 8);
            const trunkMat = new THREE.MeshStandardMaterial({ color: 0x4a2e18, roughness: 0.9 });
            const leavesGeo = new THREE.ConeGeometry(7, 18, 8);
            const leavesMat = new THREE.MeshStandardMaterial({ color: 0x1b4d24, roughness: 0.7 });

            for (let i = 0; i < count; i++) {
                const treeGroup = new THREE.Group();

                const trunk = new THREE.Mesh(trunkGeo, trunkMat);
                trunk.position.y = 7;
                treeGroup.add(trunk);

                const leaves = new THREE.Mesh(leavesGeo, leavesMat);
                leaves.position.y = 18;
                treeGroup.add(leaves);

                const angle = Math.random() * Math.PI * 2;
                const dist = 35 + Math.random() * 1200;
                treeGroup.position.set(Math.cos(angle) * dist, 0, Math.sin(angle) * dist);

                scene.add(treeGroup);
                trees.push({ group: treeGroup, trunk: trunk, health: 3, maxHealth: 3 });
            }
        }

        function generateBuildings(count) {
            const wallMat = new THREE.MeshStandardMaterial({ color: 0x222a36, roughness: 0.85, metalness: 0.2 });
            const roofMat = new THREE.MeshStandardMaterial({ color: 0x151c24, roughness: 0.9 });
            const crateMat = new THREE.MeshStandardMaterial({ color: 0xb8860b, roughness: 0.4, metalness: 0.5 });
            const glowMat = new THREE.MeshBasicMaterial({ color: 0x00ffcc });

            for (let b = 0; b < count; b++) {
                const bGroup = new THREE.Group();
                const angle = (b / count) * Math.PI * 2 + (Math.random() * 0.3);
                const dist = 100 + Math.random() * 1000;
                const bx = Math.cos(angle) * dist;
                const bz = Math.sin(angle) * dist;

                bGroup.position.set(bx, 0, bz);
                const rotY = Math.random() * Math.PI * 2;
                bGroup.rotation.y = rotY;

                const width = 24 + Math.random() * 10;
                const depth = 24 + Math.random() * 10;
                const height = 12;

                const backWall = new THREE.Mesh(new THREE.BoxGeometry(width, height, 1), wallMat);
                backWall.position.set(0, height / 2, -depth / 2);
                bGroup.add(backWall);

                const leftWall = new THREE.Mesh(new THREE.BoxGeometry(1, height, depth), wallMat);
                leftWall.position.set(-width / 2, height / 2, 0);
                bGroup.add(leftWall);

                const rightWall = new THREE.Mesh(new THREE.BoxGeometry(1, height, depth), wallMat);
                rightWall.position.set(width / 2, height / 2, 0);
                bGroup.add(rightWall);

                const frontLeft = new THREE.Mesh(new THREE.BoxGeometry(width * 0.35, height, 1), wallMat);
                frontLeft.position.set(-width * 0.32, height / 2, depth / 2);
                bGroup.add(frontLeft);

                const frontRight = new THREE.Mesh(new THREE.BoxGeometry(width * 0.35, height, 1), wallMat);
                frontRight.position.set(width * 0.32, height / 2, depth / 2);
                bGroup.add(frontRight);

                const roof = new THREE.Mesh(new THREE.BoxGeometry(width + 2, 0.8, depth + 2), roofMat);
                roof.position.set(0, height, 0);
                bGroup.add(roof);

                scene.add(bGroup);
                buildings.push(bGroup);

                // Safe zone boundary recording
                buildingBounds.push({
                    minX: bx - width / 2 - 2,
                    maxX: bx + width / 2 + 2,
                    minZ: bz - depth / 2 - 2,
                    maxZ: bz + depth / 2 + 2
                });

                // Spawn Loot Crates inside
                const crateCount = 1 + Math.floor(Math.random() * 2);
                for (let c = 0; c < crateCount; c++) {
                    const crateGroup = new THREE.Group();
                    const crateBox = new THREE.Mesh(new THREE.BoxGeometry(2.4, 2.4, 2.4), crateMat);
                    crateBox.position.y = 1.2;
                    crateGroup.add(crateBox);

                    const glowBand = new THREE.Mesh(new THREE.BoxGeometry(2.5, 0.35, 2.5), glowMat);
                    glowBand.position.y = 1.6;
                    crateGroup.add(glowBand);

                    const cx = (Math.random() - 0.5) * (width - 6);
                    const cz = (Math.random() - 0.5) * (depth - 6);

                    const worldPos = new THREE.Vector3(cx, 0, cz).applyAxisAngle(new THREE.Vector3(0, 1, 0), rotY).add(bGroup.position);
                    crateGroup.position.copy(worldPos);

                    scene.add(crateGroup);
                    lootCrates.push({ group: crateGroup, box: crateBox, glow: glowBand, looted: false });
                }
            }
        }

        // Helper check if position is inside an abandoned building safe zone
        function isPositionInsideBuilding(pos) {
            for (let b of buildingBounds) {
                if (pos.x >= b.minX && pos.x <= b.maxX && pos.z >= b.minZ && pos.z <= b.maxZ) {
                    return true;
                }
            }
            return false;
        }

        // Dynamic Dusk/Twilight theme switch every 5 levels
        function updateThemeForWave() {
            const isTwilightMode = Math.floor((wave - 1) / 5) % 2 === 1;
            if (isTwilightMode) {
                // Brighter Dusk Twilight atmosphere (dusk sky, twilight ambient glow)
                const twilightBg = new THREE.Color(0x1a2138);
                scene.background = twilightBg;
                scene.fog.color = twilightBg;
                hemiLight.color.setHex(0x9a88bb);
                hemiLight.groundColor.setHex(0x332244);
                hemiLight.intensity = 0.85; // Brighter visibility
                dirLight.color.setHex(0xffaa66);
                dirLight.intensity = 0.65;
            } else {
                // Standard Night atmosphere
                const nightBg = new THREE.Color(0x0a0d14);
                scene.background = nightBg;
                scene.fog.color = nightBg;
                hemiLight.color.setHex(0x555566);
                hemiLight.groundColor.setHex(0x111122);
                hemiLight.intensity = 0.5;
                dirLight.color.setHex(0xff7733);
                dirLight.intensity = 0.4;
            }
        }

        function generateBuildings(count) {
            const wallMat = new THREE.MeshStandardMaterial({ color: 0x222a36, roughness: 0.85, metalness: 0.2 });
            const roofMat = new THREE.MeshStandardMaterial({ color: 0x151c24, roughness: 0.9 });
            const crateMat = new THREE.MeshStandardMaterial({ color: 0xb8860b, roughness: 0.4, metalness: 0.5 });
            const glowMat = new THREE.MeshBasicMaterial({ color: 0x00ffcc });

            for (let b = 0; b < count; b++) {
                const bGroup = new THREE.Group();
                const angle = (b / count) * Math.PI * 2 + (Math.random() * 0.3);
                const dist = 80 + Math.random() * 320;
                const bx = Math.cos(angle) * dist;
                const bz = Math.sin(angle) * dist;

                bGroup.position.set(bx, 0, bz);
                bGroup.rotation.y = Math.random() * Math.PI * 2;

                const width = 20 + Math.random() * 8;
                const depth = 20 + Math.random() * 8;
                const height = 12;

                const backWall = new THREE.Mesh(new THREE.BoxGeometry(width, height, 1), wallMat);
                backWall.position.set(0, height / 2, -depth / 2);
                bGroup.add(backWall);

                const leftWall = new THREE.Mesh(new THREE.BoxGeometry(1, height, depth), wallMat);
                leftWall.position.set(-width / 2, height / 2, 0);
                bGroup.add(leftWall);

                const rightWall = new THREE.Mesh(new THREE.BoxGeometry(1, height, depth), wallMat);
                rightWall.position.set(width / 2, height / 2, 0);
                bGroup.add(rightWall);

                const frontLeft = new THREE.Mesh(new THREE.BoxGeometry(width * 0.35, height, 1), wallMat);
                frontLeft.position.set(-width * 0.32, height / 2, depth / 2);
                bGroup.add(frontLeft);

                const frontRight = new THREE.Mesh(new THREE.BoxGeometry(width * 0.35, height, 1), wallMat);
                frontRight.position.set(width * 0.32, height / 2, depth / 2);
                bGroup.add(frontRight);

                const roof = new THREE.Mesh(new THREE.BoxGeometry(width + 2, 0.8, depth + 2), roofMat);
                roof.position.set(0, height, 0);
                bGroup.add(roof);

                scene.add(bGroup);
                buildings.push(bGroup);

                // Spawn Loot Crates inside
                const crateCount = 1 + Math.floor(Math.random() * 2);
                for (let c = 0; c < crateCount; c++) {
                    const crateGroup = new THREE.Group();
                    const crateBox = new THREE.Mesh(new THREE.BoxGeometry(2.4, 2.4, 2.4), crateMat);
                    crateBox.position.y = 1.2;
                    crateGroup.add(crateBox);

                    const glowBand = new THREE.Mesh(new THREE.BoxGeometry(2.5, 0.35, 2.5), glowMat);
                    glowBand.position.y = 1.6;
                    crateGroup.add(glowBand);

                    const cx = (Math.random() - 0.5) * (width - 6);
                    const cz = (Math.random() - 0.5) * (depth - 6);

                    const worldPos = new THREE.Vector3(cx, 0, cz).applyAxisAngle(new THREE.Vector3(0, 1, 0), bGroup.rotation.y).add(bGroup.position);
                    crateGroup.position.copy(worldPos);

                    scene.add(crateGroup);
                    lootCrates.push({ group: crateGroup, box: crateBox, glow: glowBand, looted: false });
                }
            }
        }

        function createWeapons() {
            weaponGroup = new THREE.Group();

            const bodyMat = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.3, metalness: 0.8 });
            const darkMat = new THREE.MeshStandardMaterial({ color: 0x111111, roughness: 0.5 });
            const energyMat = new THREE.MeshBasicMaterial({ color: 0x00ffff });

            // 1. Pistol
            const pistolGroup = new THREE.Group();
            const pBody = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.5, 1.2), bodyMat);
            pBody.position.set(0.4, -0.4, -1.0);
            const pHandle = new THREE.Mesh(new THREE.BoxGeometry(0.25, 0.6, 0.3), darkMat);
            pHandle.position.set(0.4, -0.7, -0.7);
            pHandle.rotation.x = -0.2;
            pistolGroup.add(pBody, pHandle);
            gunMeshes['pistol'] = pistolGroup;

            // 2. Shotgun
            const shotgunGroup = new THREE.Group();
            const sBody = new THREE.Mesh(new THREE.BoxGeometry(0.4, 0.5, 2.2), bodyMat);
            sBody.position.set(0.4, -0.4, -1.3);
            const sBarrel = new THREE.Mesh(new THREE.CylinderGeometry(0.12, 0.12, 2.0), darkMat);
            sBarrel.rotation.x = Math.PI / 2;
            sBarrel.position.set(0.4, -0.3, -2.0);
            shotgunGroup.add(sBody, sBarrel);
            gunMeshes['shotgun'] = shotgunGroup;

            // 3. Rifle
            const rifleGroup = new THREE.Group();
            const rBody = new THREE.Mesh(new THREE.BoxGeometry(0.35, 0.55, 2.4), bodyMat);
            rBody.position.set(0.4, -0.4, -1.4);
            const rMag = new THREE.Mesh(new THREE.BoxGeometry(0.25, 0.8, 0.4), darkMat);
            rMag.position.set(0.4, -0.9, -1.2);
            rMag.rotation.x = 0.2;
            rifleGroup.add(rBody, rMag);
            gunMeshes['rifle'] = rifleGroup;

            // 4. Plasma
            const plasmaGroup = new THREE.Group();
            const plBody = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.6, 2.5), bodyMat);
            plBody.position.set(0.4, -0.4, -1.4);
            const plCore = new THREE.Mesh(new THREE.CylinderGeometry(0.2, 0.2, 1.5), energyMat);
            plCore.rotation.x = Math.PI / 2;
            plCore.position.set(0.4, -0.4, -1.5);
            plasmaGroup.add(plBody, plCore);
            gunMeshes['plasma'] = plasmaGroup;

            // Attach all to weapon group
            Object.keys(gunMeshes).forEach(key => {
                gunMeshes[key].visible = (key === currentWeaponKey);
                weaponGroup.add(gunMeshes[key]);
            });

            // Muzzle Flash
            const flashGeo = new THREE.PlaneGeometry(1.2, 1.2);
            const flashMat = new THREE.MeshBasicMaterial({ color: 0xffaa00, transparent: true, opacity: 0, side: THREE.DoubleSide });
            muzzleFlashMesh = new THREE.Mesh(flashGeo, flashMat);
            muzzleFlashMesh.position.set(0.4, -0.35, -2.8);
            weaponGroup.add(muzzleFlashMesh);

            camera.add(weaponGroup);
            scene.add(camera);
        }

        function onKeyDown(event) {
            if (isShopOpen && event.code !== 'KeyB') return;

            switch (event.code) {
                case 'KeyW': moveForward = true; break;
                case 'KeyA': moveLeft = true; break;
                case 'KeyS': moveBackward = true; break;
                case 'KeyD': moveRight = true; break;
                case 'Space': if (canJump) velocity.y += 320; canJump = false; break;
                case 'ShiftLeft': isRunning = true; break;
                case 'KeyR': reload(); break;
                case 'KeyF': toggleFlashlight(); break;
                case 'KeyB': toggleShop(); break;
                case 'KeyE': interact(); break;
                case 'KeyC': buildBarricade(); break;
                case 'Digit1': switchWeapon('pistol'); break;
                case 'Digit2': switchWeapon('shotgun'); break;
                case 'Digit3': switchWeapon('rifle'); break;
                case 'Digit4': switchWeapon('plasma'); break;
            }
        }

        function onKeyUp(event) {
            switch (event.code) {
                case 'KeyW': moveForward = false; break;
                case 'KeyA': moveLeft = false; break;
                case 'KeyS': moveBackward = false; break;
                case 'KeyD': moveRight = false; break;
                case 'ShiftLeft': isRunning = false; break;
            }
        }

        function onMouseDown(event) {
            if (isGameActive && !isShopOpen && event.button === 0) {
                isShooting = true;
            }
        }

        function onMouseUp(event) {
            if (event.button === 0) isShooting = false;
        }

        function onMouseMove(event) {
            if (!isGameActive || isShopOpen) return;
            const movementX = event.movementX || 0;
            const movementY = event.movementY || 0;

            const euler = new THREE.Euler(0, 0, 0, 'YXZ');
            euler.setFromQuaternion(camera.quaternion);

            euler.y -= movementX * 0.0022;
            euler.x -= movementY * 0.0022;
            euler.x = Math.max(-Math.PI / 2.05, Math.min(Math.PI / 2.05, euler.x));

            camera.quaternion.setFromEuler(euler);
        }

        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }

        function interact() {
            if (targetedTree) {
                chopTree();
            } else if (targetedCrate) {
                lootCrate();
            }
        }

        function chopTree() {
            if (!targetedTree) return;
            targetedTree.health--;
            playSound('chop');
            createParticle(targetedTree.group.position.clone().add(new THREE.Vector3(0, 5, 0)), 0x8b4513);

            if (targetedTree.health <= 0) {
                wood += 15;
                playSound('coin');
                showToast("+15 WOOD CHOPPED!");

                // Tree falls/respawns elsewhere
                scene.remove(targetedTree.group);
                trees = trees.filter(t => t !== targetedTree);
                targetedTree = null;
                document.getElementById('action-prompt').style.display = 'none';

                // Spawn replacement tree far away
                setTimeout(() => generateTrees(1), 15000);
            }
            updateHUD();
            saveAccountData();
        }

        function buildBarricade() {
            if (wood < 15) {
                showToast("NOT ENOUGH WOOD! NEED 15 WOOD");
                return;
            }

            wood -= 15;
            playSound('build');

            const wallGeo = new THREE.BoxGeometry(10, 6, 1.5);
            const wallMat = new THREE.MeshStandardMaterial({ color: 0x8b4513, roughness: 0.9 });
            const wallMesh = new THREE.Mesh(wallGeo, wallMat);

            // Position wall directly in front of camera direction
            const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(camera.quaternion);
            forward.y = 0; forward.normalize();

            wallMesh.position.copy(camera.position).addScaledVector(forward, 8);
            wallMesh.position.y = 3;
            wallMesh.quaternion.copy(camera.quaternion);
            wallMesh.rotation.x = 0; wallMesh.rotation.z = 0;

            scene.add(wallMesh);
            barricades.push({ mesh: wallMesh, health: 250, maxHealth: 250 });

            showToast("BARRICADE BUILT!");
            updateHUD();
            saveAccountData();
        }

        function lootCrate() {
            if (!targetedCrate || targetedCrate.looted) return;

            targetedCrate.looted = true;
            playSound('loot');

            const lootType = Math.floor(Math.random() * 4);
            if (lootType === 0) {
                const gainedCoins = 60 + Math.floor(Math.random() * 120);
                coins += gainedCoins;
                showToast(`LOOTED CRATE: +$${gainedCoins} COINS!`);
            } else if (lootType === 1) {
                const gainedWood = 25 + Math.floor(Math.random() * 30);
                wood += gainedWood;
                showToast(`LOOTED CRATE: +${gainedWood} WOOD!`);
            } else if (lootType === 2) {
                health = Math.min(maxHealth, health + 50);
                showToast(`LOOTED CRATE: +50 HEALTH RESTORED!`);
            } else {
                Object.keys(WEAPONS).forEach(k => ammoMap[k] = WEAPONS[k].maxAmmo);
                showToast(`LOOTED CRATE: ALL AMMO REFILLED!`);
            }

            targetedCrate.box.material = new THREE.MeshStandardMaterial({ color: 0x333333, roughness: 0.9 });
            targetedCrate.glow.material = new THREE.MeshBasicMaterial({ color: 0x111111 });

            createParticle(targetedCrate.group.position.clone().add(new THREE.Vector3(0, 2, 0)), 0x00ffcc);
            targetedCrate = null;
            document.getElementById('action-prompt').style.display = 'none';

            saveAccountData();
            updateHUD();
        }

        function switchWeapon(key) {
            if (!WEAPONS[key] || !WEAPONS[key].unlocked || isReloading) return;
            currentWeaponKey = key;
            Object.keys(gunMeshes).forEach(k => gunMeshes[k].visible = (k === key));
            updateHUD();
        }

        function toggleFlashlight() {
            if (!hasFlashlight) {
                showToast("BUY FLASHLIGHT IN SHOP (B)");
                return;
            }
            flashlightOn = !flashlightOn;
            flashlightSpot.intensity = flashlightOn ? 5.0 : 0;
            playSound('buy');
        }

        function reload() {
            const w = WEAPONS[currentWeaponKey];
            if (ammoMap[currentWeaponKey] === w.maxAmmo || isReloading) return;

            isReloading = true;
            playSound('reload');
            const msg = document.getElementById('ammo-message');
            msg.innerText = "RELOADING...";
            msg.style.display = 'block';
            msg.style.color = '#ffff00';

            setTimeout(() => {
                ammoMap[currentWeaponKey] = w.maxAmmo;
                isReloading = false;
                msg.style.display = 'none';
                updateHUD();
            }, 1400);
        }

        function shoot(now) {
            const w = WEAPONS[currentWeaponKey];
            if (now - lastShotTime < w.fireRate || isReloading) return;

            if (ammoMap[currentWeaponKey] <= 0) {
                playSound('empty');
                const msg = document.getElementById('ammo-message');
                msg.innerText = "RELOAD! (R)";
                msg.style.display = 'block';
                msg.style.color = '#ff2222';
                lastShotTime = now;
                return;
            }

            lastShotTime = now;
            ammoMap[currentWeaponKey]--;
            updateHUD();
            playSound(w.sound);

            // Recoil
            weaponGroup.position.z += 0.25;
            weaponGroup.rotation.x += 0.12;

            // Flash
            muzzleFlashMesh.opacity = 1;
            muzzleFlashMesh.rotation.z = Math.random() * Math.PI;

            // Pellets
            for (let i = 0; i < w.pelletCount; i++) {
                const spreadVec = new THREE.Vector3(
                    (Math.random() - 0.5) * w.spread,
                    (Math.random() - 0.5) * w.spread,
                    -1
                ).normalize().applyQuaternion(camera.quaternion);

                raycaster.set(camera.position, spreadVec);

                const zombieMeshes = zombies.map(z => z.mesh);
                const intersects = raycaster.intersectObjects(zombieMeshes);

                if (intersects.length > 0) {
                    const hitObj = intersects[0].object;
                    const zombie = zombies.find(z => z.mesh === hitObj);
                    if (zombie) {
                        createParticle(intersects[0].point, 0xff0000);
                        const finalDmg = w.damage * damageMultiplier;
                        zombie.health -= finalDmg;

                        if (zombie.health <= 0) {
                            killZombie(zombie);
                        } else {
                            const pushDir = hitObj.position.clone().sub(camera.position).normalize();
                            hitObj.position.add(pushDir.multiplyScalar(1.2));
                        }
                    }
                } else {
                    const floorIntersect = raycaster.intersectObject(scene.children.find(c => c.geometry && c.geometry.type === "PlaneGeometry"));
                    if (floorIntersect.length > 0) {
                        createParticle(floorIntersect[0].point, 0xffff00);
                    }
                }
            }
        }

        function createParticle(pos, color) {
            const geo = new THREE.BoxGeometry(0.25, 0.25, 0.25);
            const mat = new THREE.MeshBasicMaterial({ color: color });
            const mesh = new THREE.Mesh(geo, mat);
            mesh.position.copy(pos);
            scene.add(mesh);

            const dir = new THREE.Vector3((Math.random() - 0.5), Math.random(), (Math.random() - 0.5)).normalize();
            particles.push({ mesh: mesh, dir: dir, life: 1.0 });
        }

        function spawnZombie() {
            const angle = Math.random() * Math.PI * 2;
            const dist = 55 + Math.random() * 75;
            const x = camera.position.x + Math.cos(angle) * dist;
            const z = camera.position.z + Math.sin(angle) * dist;

            // Spawn type checks
            const isEvery5Wave = (wave % 5 === 0);
            const isMegaBoss = isEvery5Wave && (Math.random() < 0.4 || zombies.length === 0);
            const isBoss = !isMegaBoss && Math.random() < 0.18;
            const isRainbow = !isMegaBoss && !isBoss && Math.random() < 0.20; // Shiny Rainbow Mini Zombie
            const isFast = !isMegaBoss && !isBoss && !isRainbow && Math.random() < 0.25;

            let scale = isMegaBoss ? 7.5 : (isBoss ? 4.2 : (isRainbow ? 1.4 : (isFast ? 1.8 : 2.4))); 
            let height = 3.5 * scale;
            let width = 1.8 * scale;
            let zColor = isMegaBoss ? 0xff00ff : (isBoss ? 0xcc1111 : (isRainbow ? 0xffffff : (isFast ? 0xdddd22 : 0x118833)));

            // Eased difficulty scaling
            let zHealth = (isMegaBoss ? 1500 : (isBoss ? 500 : (isRainbow ? 120 : 130))) + (wave * 35);
            let zDamage = (isMegaBoss ? 25 : (isBoss ? 16 : 8)) + (wave * 2);
            let zSpeed = isFast ? baseZombieSpeed * 1.35 : (isMegaBoss ? baseZombieSpeed * 0.65 : (isBoss ? baseZombieSpeed * 0.8 : baseZombieSpeed));

            const geometry = new THREE.BoxGeometry(width, height, width);
            const material = new THREE.MeshStandardMaterial({
                color: zColor,
                roughness: isRainbow ? 0.2 : 0.6,
                metalness: isRainbow ? 0.8 : 0.1
            });
            const zombieMesh = new THREE.Mesh(geometry, material);
            zombieMesh.position.set(x, height / 2, z);

            const eyeGeo = new THREE.BoxGeometry(0.35 * scale, 0.35 * scale, 0.35 * scale);
            const eyeMat = new THREE.MeshBasicMaterial({ color: isMegaBoss ? 0x00ffff : (isBoss ? 0xffff00 : 0xff0000) });
            const eye1 = new THREE.Mesh(eyeGeo, eyeMat);
            const eye2 = new THREE.Mesh(eyeGeo, eyeMat);
            eye1.position.set(width * 0.25, height * 0.35, width * 0.5);
            eye2.position.set(-width * 0.25, height * 0.35, width * 0.5);
            zombieMesh.add(eye1, eye2);

            scene.add(zombieMesh);
            zombies.push({
                mesh: zombieMesh,
                health: zHealth,
                maxHealth: zHealth,
                damage: zDamage,
                speed: zSpeed,
                isBoss: isBoss || isMegaBoss,
                isMegaBoss: isMegaBoss,
                isRainbow: isRainbow,
                value: isMegaBoss ? 2500 : (isBoss ? 1000 : (isRainbow ? 650 : 35)) // High rewards for bosses and rainbow mini-zombies
            });
        }

        function killZombie(zombie) {
            scene.remove(zombie.mesh);
            zombies = zombies.filter(z => z !== zombie);

            score += zombie.value * 10;
            coins += zombie.value;

            playSound('zombie_die');
            playSound('coin');

            if (zombie.isMegaBoss) {
                showToast(`🏆 MEGA BOSS DEFEATED! +$${zombie.value} COINS!`);
            } else if (zombie.isBoss) {
                showToast(`💥 BOSS ELIMINATED! +$${zombie.value} COINS!`);
            } else if (zombie.isRainbow) {
                showToast(`🌈 SHINY RAINBOW ZOMBIE KILLED! +$${zombie.value} COINS!`);
            }

            if (score >= wave * 1500) {
                wave++;
                spawnRate = Math.max(250, spawnRate - 80);
                health = Math.min(maxHealth, health + 50);
                updateThemeForWave();
                showToast(`WAVE ${wave} STARTED! ENVIRONMENT SHIFTED.`);
            }

            saveAccountData();
            updateHUD();
            updateShopUI();
        }

        function takeDamage(amount) {
            health -= amount;
            playSound('hit');

            const overlay = document.getElementById('damage-overlay');
            overlay.classList.add('damage-active');
            setTimeout(() => overlay.classList.remove('damage-active'), 200);

            if (health <= 0) {
                health = 0;
                gameOver();
            }
            updateHUD();
        }

        function gameOver() {
            isGameActive = false;
            document.exitPointerLock();
            document.getElementById('blocker').style.display = 'flex';
            document.getElementById('title-text').innerText = "YOU DIED";
            document.getElementById('title-text').style.color = "#888";
            document.getElementById('instructions').querySelector('p').innerText = "FINAL SCORE: " + score + " - CLICK TO RESTART";
        }

        function resetGame() {
            health = maxHealth;
            score = 0;
            wave = 1;
            Object.keys(WEAPONS).forEach(k => ammoMap[k] = WEAPONS[k].maxAmmo);

            zombies.forEach(z => scene.remove(z.mesh));
            zombies = [];

            // Spawn fresh wave of zombies on restart
            for (let i = 0; i < 8; i++) {
                spawnZombie();
            }

            updateHUD();
            document.getElementById('title-text').innerText = "HARDCORE ZOMBIE SECTOR";
            document.getElementById('title-text').style.color = "#ff1111";
        }

        function toggleShop(forceState) {
            if (forceState !== undefined) {
                isShopOpen = forceState;
            } else {
                isShopOpen = !isShopOpen;
            }

            const shopEl = document.getElementById('shop-overlay');
            if (isShopOpen) {
                shopEl.style.display = 'flex';
                document.exitPointerLock();
                updateShopUI();
            } else {
                shopEl.style.display = 'none';
                if (isGameActive) document.body.requestPointerLock();
            }
        }

        function buyItem(item) {
            if (item === 'shotgun' && coins >= 150) {
                coins -= 150; WEAPONS.shotgun.unlocked = true; switchWeapon('shotgun'); playSound('buy'); showToast("UNLOCKED SHOTGUN!");
            } else if (item === 'rifle' && coins >= 350) {
                coins -= 350; WEAPONS.rifle.unlocked = true; switchWeapon('rifle'); playSound('buy'); showToast("UNLOCKED ASSAULT RIFLE!");
            } else if (item === 'plasma' && coins >= 750) {
                coins -= 750; WEAPONS.plasma.unlocked = true; switchWeapon('plasma'); playSound('buy'); showToast("UNLOCKED PLASMA CANNON!");
            } else if (item === 'flashlight' && coins >= 50) {
                coins -= 50; hasFlashlight = true; toggleFlashlight(); showToast("ULTRA FLASHLIGHT UNLOCKED (F)!");
            } else if (item === 'rear_sensor' && coins >= 125) {
                coins -= 125; hasRearSensor = true; document.getElementById('radar-container').style.display = 'block'; playSound('buy'); showToast("REAR RADAR SENSOR ACTIVE!");
            } else if (item === 'health' && coins >= 100) {
                if (maxHealth >= 500) {
                    showToast("MAX HEALTH ALREADY AT LIMIT (500 HP)!"); return;
                }
                coins -= 100; maxHealth += 50; health = maxHealth; playSound('buy'); showToast(`MAX HEALTH UPGRADED TO ${maxHealth} HP!`);
            } else if (item === 'speed' && coins >= 120) {
                coins -= 120; speedMultiplier += 0.25; playSound('buy'); showToast("SPRINT SPEED +25%!");
            } else if (item === 'damage' && coins >= 200) {
                coins -= 200; damageMultiplier += 0.50; playSound('buy'); showToast("DAMAGE FIREPOWER +50%!");
            } else if (item === 'wood_pack' && coins >= 50) {
                coins -= 50; wood += 30; playSound('buy'); showToast("+30 WOOD SUPPLIES!");
            } else {
                showToast("NOT ENOUGH COINS!"); return;
            }

            saveAccountData();
            updateHUD();
            updateShopUI();
        }

        function updateShopUI() {
            document.getElementById('shop-coin-count').innerText = `COINS: $${coins} | WOOD: ${wood}`;

            document.getElementById('buy-shotgun').disabled = WEAPONS.shotgun.unlocked || coins < 150;
            if (WEAPONS.shotgun.unlocked) document.getElementById('buy-shotgun').innerText = "UNLOCKED";

            document.getElementById('buy-rifle').disabled = WEAPONS.rifle.unlocked || coins < 350;
            if (WEAPONS.rifle.unlocked) document.getElementById('buy-rifle').innerText = "UNLOCKED";

            document.getElementById('buy-plasma').disabled = WEAPONS.plasma.unlocked || coins < 750;
            if (WEAPONS.plasma.unlocked) document.getElementById('buy-plasma').innerText = "UNLOCKED";

            document.getElementById('buy-flashlight').disabled = hasFlashlight || coins < 50;
            if (hasFlashlight) document.getElementById('buy-flashlight').innerText = "UNLOCKED";

            document.getElementById('buy-rear-sensor').disabled = hasRearSensor || coins < 125;
            if (hasRearSensor) document.getElementById('buy-rear-sensor').innerText = "UNLOCKED";

            document.getElementById('buy-health').disabled = maxHealth >= 500 || coins < 100;
            if (maxHealth >= 500) document.getElementById('buy-health').innerText = "MAX (500 HP)";
        }

        function updateHUD() {
            document.getElementById('score').innerText = `SCORE: ${score}`;
            document.getElementById('coins').innerText = `COINS: $${coins}`;
            document.getElementById('wood').innerText = `🪵 WOOD: ${wood}`;
            document.getElementById('wave').innerText = `WAVE: ${wave}`;

            const w = WEAPONS[currentWeaponKey];
            document.getElementById('weapon-name').innerText = w.name;
            document.getElementById('ammo-display').innerText = `${ammoMap[currentWeaponKey]} / ${w.maxAmmo}`;

            const healthPct = Math.max(0, (health / maxHealth) * 100);
            document.getElementById('health-bar').style.width = `${healthPct}%`;
            document.getElementById('health-num').innerText = `${Math.ceil(health)}/${maxHealth}`;
        }

        function renderRadar() {
            if (!hasRearSensor) return;
            const canvas = document.getElementById('radar-canvas');
            const ctx = canvas.getContext('2d');
            const cx = 70, cy = 70, radius = 65;

            ctx.clearRect(0, 0, 140, 140);

            // Background circle
            ctx.fillStyle = 'rgba(0, 30, 0, 0.7)';
            ctx.beginPath(); ctx.arc(cx, cy, radius, 0, Math.PI * 2); ctx.fill();

            // Radar Grid
            ctx.strokeStyle = 'rgba(0, 255, 102, 0.3)'; ctx.lineWidth = 1;
            ctx.beginPath(); ctx.arc(cx, cy, radius * 0.5, 0, Math.PI * 2); ctx.stroke();
            ctx.beginPath(); ctx.moveTo(cx, cy - radius); ctx.lineTo(cx, cy + radius); ctx.stroke();
            ctx.beginPath(); ctx.moveTo(cx - radius, cy); ctx.lineTo(cx + radius, cy); ctx.stroke();

            // Player Center Triangle
            ctx.fillStyle = '#00ff66';
            ctx.beginPath();
            ctx.moveTo(cx, cy - 6); ctx.lineTo(cx - 5, cy + 5); ctx.lineTo(cx + 5, cy + 5);
            ctx.closePath(); ctx.fill();

            // Player Forward Quaternion Vector
            const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(camera.quaternion);
            const pAngle = Math.atan2(forward.x, forward.z);

            let rearThreat = false;

            // Plot Zombies
            zombies.forEach(z => {
                const relX = z.mesh.position.x - camera.position.x;
                const relZ = z.mesh.position.z - camera.position.z;
                const dist = Math.sqrt(relX * relX + relZ * relZ);

                if (dist < 120) {
                    const zAngle = Math.atan2(relX, relZ);
                    const diffAngle = zAngle - pAngle;

                    const mapDist = (dist / 120) * radius;
                    const mapX = cx + Math.sin(diffAngle) * mapDist;
                    const mapY = cy - Math.cos(diffAngle) * mapDist;

                    ctx.fillStyle = z.isBoss ? '#ffff00' : '#ff0000';
                    ctx.beginPath();
                    ctx.arc(mapX, mapY, z.isBoss ? 5 : 3, 0, Math.PI * 2);
                    ctx.fill();

                    // Check if behind player (Angle > 110 degrees) and within 45 units
                    const deg = Math.abs(diffAngle * (180 / Math.PI));
                    if ((deg > 110 && deg < 250) && dist < 45) {
                        rearThreat = true;
                    }
                }
            });

            document.getElementById('rear-warning').style.display = rearThreat ? 'block' : 'none';
        }

        function saveAccountData() {
            const data = {
                coins: coins,
                wood: wood,
                maxHealth: maxHealth,
                speedMultiplier: speedMultiplier,
                damageMultiplier: damageMultiplier,
                hasFlashlight: hasFlashlight,
                hasRearSensor: hasRearSensor,
                unlockedWeapons: {
                    shotgun: WEAPONS.shotgun.unlocked,
                    rifle: WEAPONS.rifle.unlocked,
                    plasma: WEAPONS.plasma.unlocked
                }
            };
            localStorage.setItem('zombie_fps_account', JSON.stringify(data));
        }

        function loadAccountData() {
            const raw = localStorage.getItem('zombie_fps_account');
            if (!raw) return;
            try {
                const data = JSON.parse(raw);
                coins = data.coins || 0;
                wood = data.wood || 20;
                maxHealth = data.maxHealth || 100;
                health = maxHealth;
                speedMultiplier = data.speedMultiplier || 1.0;
                damageMultiplier = data.damageMultiplier || 1.0;
                hasFlashlight = !!data.hasFlashlight;
                hasRearSensor = !!data.hasRearSensor;

                if (data.unlockedWeapons) {
                    WEAPONS.shotgun.unlocked = !!data.unlockedWeapons.shotgun;
                    WEAPONS.rifle.unlocked = !!data.unlockedWeapons.rifle;
                    WEAPONS.plasma.unlocked = !!data.unlockedWeapons.plasma;
                }

                if (hasRearSensor) {
                    document.getElementById('radar-container').style.display = 'block';
                }
                updateHUD();
            } catch (e) {
                console.error("Failed to load save data", e);
            }
        }

        function resetAccountData() {
            localStorage.removeItem('zombie_fps_account');
            coins = 5000; // Reset with $5,000 deposit
            wood = 20;
            maxHealth = 100;
            health = 100;
            wave = 1;
            speedMultiplier = 1.0;
            damageMultiplier = 1.0;
            hasFlashlight = false;
            hasRearSensor = false;

            flashlightSpot.intensity = 0;
            flashlightOn = false;
            document.getElementById('radar-container').style.display = 'none';

            WEAPONS.shotgun.unlocked = false;
            WEAPONS.rifle.unlocked = false;
            WEAPONS.plasma.unlocked = false;
            switchWeapon('pistol');

            updateThemeForWave();
            showToast("ACCOUNT RESET! $5,000 CASH DEPOSITED.");
            saveAccountData();
            updateHUD();
            updateShopUI();
        }

        function showToast(msg) {
            const toast = document.getElementById('toast-msg');
            toast.innerText = msg;
            toast.style.display = 'block';
            setTimeout(() => toast.style.display = 'none', 2600);
        }

        function animate() {
            requestAnimationFrame(animate);

            if (isGameActive && !isShopOpen) {
                const time = performance.now();
                const delta = (time - prevTime) / 1000;

                // Automatic enemy spawner loop
                if (time - lastSpawnTime > spawnRate && zombies.length < 25) {
                    spawnZombie();
                    lastSpawnTime = time;
                }

                // Physics movement
                velocity.x -= velocity.x * 10.0 * delta;
                velocity.z -= velocity.z * 10.0 * delta;
                velocity.y -= 9.8 * 100.0 * delta;

                direction.z = Number(moveForward) - Number(moveBackward);
                direction.x = Number(moveRight) - Number(moveLeft);
                direction.normalize();

                const currentSpeed = (isRunning ? playerBaseSpeed * 1.7 : playerBaseSpeed) * speedMultiplier;

                if (moveForward || moveBackward) velocity.z -= direction.z * 400.0 * delta * (currentSpeed / 40);
                if (moveLeft || moveRight) velocity.x += direction.x * 400.0 * delta * (currentSpeed / 40);

                camera.translateX(velocity.x * delta);
                camera.translateZ(velocity.z * delta);
                camera.position.y += (velocity.y * delta);

                if (camera.position.y < 10) {
                    velocity.y = 0; camera.position.y = 10; canJump = true;
                }

                // Raycast target check for trees and loot crates
                raycaster.setFromCamera(new THREE.Vector2(0, 0), camera);

                const treeTrunks = trees.map(t => t.trunk);
                const crateBoxes = lootCrates.filter(c => !c.looted).map(c => c.box);

                const treeHits = raycaster.intersectObjects(treeTrunks);
                const crateHits = raycaster.intersectObjects(crateBoxes);

                const promptEl = document.getElementById('action-prompt');

                if (treeHits.length > 0 && treeHits[0].distance < 18) {
                    const hitTrunk = treeHits[0].object;
                    targetedTree = trees.find(t => t.trunk === hitTrunk);
                    targetedCrate = null;
                    promptEl.innerText = "PRESS [E] TO CHOP TREE";
                    promptEl.style.display = 'block';
                } else if (crateHits.length > 0 && crateHits[0].distance < 18) {
                    const hitBox = crateHits[0].object;
                    targetedCrate = lootCrates.find(c => c.box === hitBox);
                    targetedTree = null;
                    promptEl.innerText = "PRESS [E] TO LOOT CRATE";
                    promptEl.style.display = 'block';
                } else {
                    targetedTree = null;
                    targetedCrate = null;
                    promptEl.style.display = 'none';
                }

                // Weapon recoil recovery & sway
                weaponGroup.position.z += (0 - weaponGroup.position.z) * 12 * delta;
                weaponGroup.rotation.x += (0 - weaponGroup.rotation.x) * 12 * delta;
                if (muzzleFlashMesh.opacity > 0) muzzleFlashMesh.opacity -= delta * 18;
                // Zombie AI - Persistent tracking & Structure Safe Zones
                const isPlayerInsideBuilding = isPositionInsideBuilding(camera.position);

                zombies.forEach(z => {
                    const zObj = z.mesh;

                    // Animate Rainbow Mini Zombies
                    if (z.isRainbow && zObj.material) {
                        const hue = (time * 0.001) % 1;
                        zObj.material.color.setHSL(hue, 1.0, 0.5);
                    }

                    let targetBarricade = null;
                    let minDist = 999;
                    barricades.forEach(b => {
                        const bDist = zObj.position.distanceTo(b.mesh.position);
                        if (bDist < 6 && bDist < minDist) {
                            minDist = bDist;
                            targetBarricade = b;
                        }
                    });

                    if (targetBarricade) {
                        targetBarricade.health -= delta * 30;
                        if (targetBarricade.health <= 0) {
                            scene.remove(targetBarricade.mesh);
                            barricades = barricades.filter(b => b !== targetBarricade);
                            playSound('chop');
                        }
                    } else if (!isPlayerInsideBuilding) {
                        // Only pursue player if player is NOT inside an abandoned building structure (Safe Zone)
                        zObj.lookAt(camera.position.x, zObj.position.y, camera.position.z);
                        const dist = zObj.position.distanceTo(camera.position);

                        if (dist > (z.isMegaBoss ? 8.0 : (z.isBoss ? 5.5 : 3.8))) {
                            zObj.translateZ(z.speed * delta);
                        } else {
                            if (Math.random() < 0.05) takeDamage(z.damage);
                        }
                    } else {
                        // Zombies stop at structure perimeters when player is safely inside
                        const distToPlayer = zObj.position.distanceTo(camera.position);
                        if (distToPlayer > 30) {
                            zObj.lookAt(camera.position.x, zObj.position.y, camera.position.z);
                            zObj.translateZ(z.speed * d elta * 0.3);
                        }
                    }
                });

                // Particles loop
                for (let i = particles.length - 1; i >= 0; i--) {
                    let p = particles[i];
                    p.mesh.position.addScaledVector(p.dir, delta * 12);
                    p.life -= delta * 2.5;
                    p.mesh.scale.setScalar(p.life);
                    if (p.life <= 0) {
                        scene.remove(p.mesh);
                        particles.splice(i, 1);
                    }
                }

                renderRadar();
                prevTime = time;
            }

            renderer.render(scene, camera);
        }
    </script>
</body>
</html>

72,872 chars · codepastes.com