8 Ball Cheat

Cheat for any 8 ball pool game!

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name         8 Ball Cheat
// @namespace    http://tampermonkey.net/
// @version      1.0
// @license MIT
// @description  Cheat for any 8 ball pool game!
// @author       Alex
// @match        https://www.crazygames.com/game/8-ball-pool-billiards-multiplayer*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    const SCRIPT_ID = 'Advanced8Ball_Pro_v4';

    // --- Styling ---
    if (typeof GM_addStyle !== 'undefined') {
        GM_addStyle(`
            #${SCRIPT_ID}_SettingsPanel {
                position: fixed; background: rgba(20, 20, 20, 0.9); color: #efefef;
                padding: 15px; border-radius: 10px; z-index: 10005; min-width: 260px;
                font-family: 'Segoe UI', Tahoma, sans-serif; box-shadow: 0 4px 15px rgba(0,0,0,0.5);
                border: 1px solid #444; backdrop-filter: blur(4px);
            }
            #${SCRIPT_ID}_PanelHeader { cursor: move; font-weight: bold; border-bottom: 1px solid #444; padding-bottom: 8px; margin-bottom: 10px; text-align: center; color: #00ffcc; }
            .helper-btn { width: 100%; margin-top: 8px; padding: 6px; cursor: pointer; background: #333; color: white; border: 1px solid #555; border-radius: 4px; transition: 0.2s; font-size: 12px; }
            .helper-btn:hover { background: #444; border-color: #00ffcc; }
            .helper-btn.active { background: #a82323; border-color: #ff4444; color: white; font-weight: bold; }
            .helper-label { display: block; margin-top: 8px; font-size: 13px; color: #bbb; }
            .marker-active { outline: 2px solid gold !important; box-shadow: 0 0 10px gold; }
            .profile-input { width: 100%; padding: 5px; box-sizing: border-box; background: #222; border: 1px solid #555; color: #fff; border-radius: 4px; margin-top: 4px; font-size: 12px; }
            .profile-select { width: 100%; padding: 5px; box-sizing: border-box; background: #222; border: 1px solid #555; color: #00ffcc; border-radius: 4px; margin-top: 4px; font-size: 12px; }
            .profile-row { display: flex; gap: 5px; margin-top: 5px; }
            .profile-row button { margin-top: 0; }
            #${SCRIPT_ID}_PanBlocker {
                position: fixed; z-index: 10001; background: rgba(0, 255, 204, 0.05);
                border: 2px dashed #00ffcc; cursor: move; display: none; box-sizing: border-box;
            }
        `);
    }

    let config = {
        calibPoints: [{x:50,y:50}, {x:750,y:50}, {x:750,y:450}, {x:50,y:450}],
        cueBallPos: { x: 400, y: 300 },
        lineWidth: 2,
        guideLineColor: 'rgba(255, 255, 255, 0.2)',
        ballRadius: 11,
        calibrationLocked: false,
        showGhostBall: true,
        settingsPanelVisible: true,
        settingsPanelPos: { top: '20px', left: '20px' }
    };

    let gameIframe = null, overlayCanvas = null, ctx = null, settingsPanel = null;
    let cueBallMarker = null, calibMarkers = [], cachedPockets = [];
    let activeElement = null;
    let isPanningMode = false, panBlocker = null;
    let manualPocketOverride = -1;

    function iframeRect() { return gameIframe?.getBoundingClientRect(); }

    function updateCachedPockets() {
        if (config.calibPoints.length !== 4) return;
        const [tl, tr, br, bl] = config.calibPoints;
        cachedPockets = [
            { x: tl.x, y: tl.y, name: "Sup. Esquerda (1)" },
            { x: tr.x, y: tr.y, name: "Sup. Direita (2)" },
            { x: br.x, y: br.y, name: "Inf. Direita (3)" },
            { x: bl.x, y: bl.y, name: "Inf. Esquerda (4)" },
            { x: (tl.x + tr.x) / 2, y: (tl.y + tr.y) / 2, name: "Meio Sup. (5)" },
            { x: (bl.x + br.x) / 2, y: (bl.y + br.y) / 2, name: "Meio Inf. (6)" }
        ];
    }

    function save() { if (typeof GM_setValue !== 'undefined') GM_setValue(SCRIPT_ID + '_cfg', JSON.stringify(config)); }
    function load() {
        if (typeof GM_getValue !== 'undefined') {
            const saved = GM_getValue(SCRIPT_ID + '_cfg');
            if (saved) { try { Object.assign(config, JSON.parse(saved)); } catch(e) {} }
        }
    }

    function getProfiles() {
        const profiles = localStorage.getItem(SCRIPT_ID + '_profiles');
        return profiles ? JSON.parse(profiles) : {};
    }

    document.body.addEventListener('click', function(e) {
        if (e.target && e.target.id === 'save_profile') {
            const name = document.getElementById('profile_name').value;
            if (!name.trim()) return alert("Por favor digite um nome válido.");
            const profiles = getProfiles();
            profiles[name] = { calibPoints: config.calibPoints, cueBallPos: config.cueBallPos, lineWidth: config.lineWidth, ballRadius: config.ballRadius, showGhostBall: config.showGhostBall };
            localStorage.setItem(SCRIPT_ID + '_profiles', JSON.stringify(profiles));
            updateProfileDropdown();
            document.getElementById('profile_name').value = '';
        }
        if (e.target && e.target.id === 'load_profile') {
            const name = document.getElementById('profile_select').value;
            if (!name) return;
            const profiles = getProfiles();
            if (profiles[name]) {
                Object.assign(config, profiles[name]);
                document.getElementById('lw_in').value = config.lineWidth;
                document.getElementById('lw_val').innerText = config.lineWidth;
                document.getElementById('bs_in').value = config.ballRadius;
                document.getElementById('gb_toggle').checked = config.showGhostBall;
                updateCachedPockets(); updateMarkersPosition(); draw(); save();
            }
        }
        if (e.target && e.target.id === 'delete_profile') {
            const name = document.getElementById('profile_select').value;
            if (!name) return;
            const profiles = getProfiles();
            if (profiles[name]) {
                delete profiles[name];
                localStorage.setItem(SCRIPT_ID + '_profiles', JSON.stringify(profiles));
                updateProfileDropdown();
            }
        }
    });

    function updateProfileDropdown() {
        const select = document.getElementById('profile_select'); if (!select) return;
        select.innerHTML = '<option value="">-- Carregar Formato Salvo --</option>';
        const profiles = getProfiles();
        for (let name in profiles) {
            const opt = document.createElement('option'); opt.value = name; opt.innerText = name; select.appendChild(opt);
        }
    }

    function createPanel() {
        settingsPanel = document.createElement('div');
        settingsPanel.id = SCRIPT_ID + '_SettingsPanel';
        settingsPanel.style.top = config.settingsPanelPos.top;
        settingsPanel.style.left = config.settingsPanelPos.left;
        settingsPanel.style.display = config.settingsPanelVisible ? 'block' : 'none';
        settingsPanel.innerHTML = `
            <div id="${SCRIPT_ID}_PanelHeader">8 BALL PRO HELPER</div>
            <label class="helper-label">Line Width: <span id="lw_val">${config.lineWidth}</span></label>
            <input type="range" id="lw_in" min="1" max="5" value="${config.lineWidth}" style="width:100%">
            <label class="helper-label">Ball Scale (Ghost Ball Size):</label>
            <input type="range" id="bs_in" min="5" max="25" value="${config.ballRadius}" style="width:100%">
            <label class="helper-label"><input type="checkbox" id="gb_toggle" ${config.showGhostBall ? 'checked' : ''}> Show Ghost Balls</label>
            <label class="helper-label"><input type="checkbox" id="lock_toggle" ${config.calibrationLocked ? 'checked' : ''}> Lock Calibration</label>
            <button id="toggle_pan" class="helper-btn">Move Whole Table Grid</button>
            <button id="reset_cal" class="helper-btn">Reset Grid</button>
            <hr style="border: 0; border-top: 1px solid #444; margin: 12px 0 8px 0;">
            <label class="helper-label" style="color: #00ffcc;">Format Configuration Manager</label>
            <input type="text" id="profile_name" class="profile-input" placeholder="Nome do layout...">
            <button id="save_profile" class="helper-btn" style="background: #2a5236; border-color: #3b7a4e;">Salvar Formato Atual</button>
            <select id="profile_select" class="profile-select"></select>
            <div class="profile-row">
                <button id="load_profile" class="helper-btn" style="background: #28446b; border-color: #3b6299;">Carregar</button>
                <button id="delete_profile" class="helper-btn" style="background: #5c2626; border-color: #823636;">Excluir</button>
            </div>
            <div style="font-size:10px; color:#aaa; margin-top:10px; border-top: 1px solid #333; padding-top: 5px;">
                <b style="color:#00ffcc;">Controle de Exceção Dinâmico:</b><br>
                Teclas <span style="color:#fff; background:#333; padding:1px 4px; border-radius:3px;">1 a 6</span>: Foca mira em uma única caçapa.<br>
                Tecla <span style="color:#fff; background:#333; padding:1px 4px; border-radius:3px;">0</span>: Reseta e mostra todas as caçapas.<br>
                <span id="active_pocket_status" style="color:#ffcc00; display:block; margin-top:4px;">Modo: Todas as Caçapas</span>
            </div>
        `;
        document.body.appendChild(settingsPanel);
        document.getElementById('lw_in').oninput = (e) => { config.lineWidth = parseInt(e.target.value); document.getElementById('lw_val').innerText = config.lineWidth; draw(); };
        document.getElementById('bs_in').oninput = (e) => { config.ballRadius = parseInt(e.target.value); draw(); };
        document.getElementById('gb_toggle').onchange = (e) => { config.showGhostBall = e.target.checked; draw(); };
        document.getElementById('lock_toggle').onchange = (e) => { config.calibrationLocked = e.target.checked; updateMarkersVisibility(); save(); };
        document.getElementById('toggle_pan').onclick = toggleTablePanning;
        document.getElementById('reset_cal').onclick = () => { resetCalib(); save(); draw(); };
        updateProfileDropdown();
        setupDraggable(document.getElementById(`${SCRIPT_ID}_PanelHeader`), settingsPanel, 'panel');
    }

    function toggleTablePanning() {
        const btn = document.getElementById('toggle_pan'); isPanningMode = !isPanningMode;
        if (isPanningMode) { btn.classList.add('active'); btn.innerText = 'LOCK GRID POSITION'; panBlocker.style.display = 'block'; syncPanBlockerSize(); }
        else { btn.classList.remove('active'); btn.innerText = 'Move Whole Table Grid'; panBlocker.style.display = 'none'; }
    }

    function syncPanBlockerSize() {
        const r = iframeRect(); if (!r || !panBlocker) return;
        panBlocker.style.left = r.left + 'px'; panBlocker.style.top = r.top + 'px'; panBlocker.style.width = r.width + 'px'; panBlocker.style.height = r.height + 'px';
    }

    function resetCalib() {
        config.calibPoints = [{x:50,y:50}, {x:750,y:50}, {x:750,y:450}, {x:50,y:450}]; config.cueBallPos = { x: 400, y: 300 };
        updateCachedPockets(); updateMarkersPosition(); if (isPanningMode) syncPanBlockerSize();
    }

    function setupDraggable(handle, target, type, index = -1) {
        handle.onpointerdown = (e) => {
            handle.setPointerCapture(e.pointerId); const startX = e.clientX, startY = e.clientY;
            const initialX = type === 'panel' ? target.offsetLeft : (type === 'cue' ? config.cueBallPos.x : (index !== -1 ? config.calibPoints[index].x : 0));
            const initialY = type === 'panel' ? target.offsetTop : (type === 'cue' ? config.cueBallPos.y : (index !== -1 ? config.calibPoints[index].y : 0));
            const initialCalibPoints = config.calibPoints.map(p => ({ ...p })); const initialCueBall = { ...config.cueBallPos };
            document.querySelectorAll('.marker-active').forEach(m => m.classList.remove('marker-active'));
            if (type !== 'panel' && type !== 'table_pan') { handle.classList.add('marker-active'); activeElement = { type, index }; }
            handle.onpointermove = (moveEvt) => {
                const dx = moveEvt.clientX - startX; const dy = moveEvt.clientY - startY;
                if (type === 'panel') { target.style.left = (initialX + dx) + 'px'; target.style.top = (initialY + dy) + 'px'; config.settingsPanelPos = { top: target.style.top, left: target.style.left }; }
                else if (type === 'table_pan') { config.calibPoints = initialCalibPoints.map(p => ({ x: p.x + dx, y: p.y + dy })); config.cueBallPos = { x: initialCueBall.x + dx, y: initialCueBall.y + dy }; updateCachedPockets(); updateMarkersPosition(); draw(); }
                else {
                    const newX = initialX + dx; const newY = initialY + dy;
                    if (type === 'cue') { config.cueBallPos = { x: newX, y: newY }; } else { config.calibPoints[index] = { x: newX, y: newY }; updateCachedPockets(); }
                    updateMarkersPosition(); draw();
                }
            };
            handle.onpointerup = () => { handle.onpointermove = null; save(); };
        };
    }

    function createMarkers() {
        overlayCanvas = document.createElement('canvas');
        Object.assign(overlayCanvas.style, { position:'fixed', top:'0', left:'0', zIndex:'10000', pointerEvents:'none' });
        document.body.appendChild(overlayCanvas);
        ctx = overlayCanvas.getContext('2d');
        panBlocker = document.createElement('div');
        panBlocker.id = SCRIPT_ID + '_PanBlocker';
        document.body.appendChild(panBlocker);
        setupDraggable(panBlocker, null, 'table_pan');
        cueBallMarker = document.createElement('div');
        Object.assign(cueBallMarker.style, { position:'fixed', width:'20px', height:'20px', border:'2px solid red', borderRadius:'50%', zIndex:'10003', cursor:'move' });
        document.body.appendChild(cueBallMarker);
        setupDraggable(cueBallMarker, null, 'cue');
        for (let i = 0; i < 4; i++) {
            const m = document.createElement('div');
            Object.assign(m.style, { position:'fixed', width:'12px', height:'12px', background:'lime', zIndex:'10003', cursor:'move' });
            document.body.appendChild(m);
            setupDraggable(m, null, 'calib', i);
            calibMarkers.push(m);
        }
        updateMarkersVisibility();
        updateMarkersPosition();
    }

    function updateMarkersPosition() {
        const r = iframeRect(); if (!r) return;
        cueBallMarker.style.left = (r.left + config.cueBallPos.x - 10) + 'px'; cueBallMarker.style.top = (r.top + config.cueBallPos.y - 10) + 'px';
        calibMarkers.forEach((m, i) => { m.style.left = (r.left + config.calibPoints[i].x - 6) + 'px'; m.style.top = (r.top + config.calibPoints[i].y - 6) + 'px'; });
    }

    function updateMarkersVisibility() { calibMarkers.forEach(m => m.style.display = config.calibrationLocked ? 'none' : 'block'); }

    function draw() {
        if (!ctx || !gameIframe) return;
        const r = iframeRect(); overlayCanvas.width = window.innerWidth; overlayCanvas.height = window.innerHeight;
        ctx.save(); ctx.translate(r.left, r.top);
        if (!config.calibrationLocked) {
            ctx.strokeStyle = config.guideLineColor; ctx.lineWidth = 1; ctx.beginPath();
            ctx.moveTo(config.calibPoints[0].x, config.calibPoints[0].y);
            config.calibPoints.forEach(p => ctx.lineTo(p.x, p.y)); ctx.closePath(); ctx.stroke();
        }
        let pocketDistances = cachedPockets.map((p, idx) => {
            let dx = p.x - config.cueBallPos.x; let dy = p.y - config.cueBallPos.y;
            return { index: idx, distance: Math.sqrt(dx * dx + dy * dy), pocket: p };
        });
        let sortedPockets = [...pocketDistances].sort((a, b) => a.distance - b.distance);
        let closestIndex = sortedPockets[0]?.index;
        let maxDist = sortedPockets[sortedPockets.length - 1]?.distance || 1;
        pocketDistances.forEach(item => {
            let p = item.pocket; let idx = item.index;
            if (manualPocketOverride !== -1 && manualPocketOverride !== idx) return;
            let currentLineColor = '#ff3333';
            if (idx === closestIndex) currentLineColor = '#00ff55';
            else if (item.distance < maxDist * 0.65) currentLineColor = '#ffcc00';
            ctx.beginPath();
            if (manualPocketOverride === idx) { ctx.setLineDash([]); ctx.lineWidth = config.lineWidth + 1; }
            else { ctx.setLineDash([5, 5]); ctx.lineWidth = config.lineWidth; }
            ctx.strokeStyle = currentLineColor;
            ctx.moveTo(config.cueBallPos.x, config.cueBallPos.y);
            ctx.lineTo(p.x, p.y); ctx.stroke(); ctx.setLineDash([]);
            if (config.showGhostBall) {
                ctx.beginPath();
                ctx.arc(p.x, p.y, config.ballRadius + (manualPocketOverride === idx ? 2 : 0), 0, Math.PI * 2);
                ctx.fillStyle = currentLineColor === '#00ff55' ? 'rgba(0, 255, 85, 0.15)' : (currentLineColor === '#ffcc00' ? 'rgba(255, 204, 0, 0.15)' : 'rgba(255, 51, 51, 0.15)');
                ctx.fill(); ctx.strokeStyle = currentLineColor; ctx.stroke();
            }
        });
        ctx.restore();
    }

    window.addEventListener('keydown', (e) => {
        const key = e.key.toLowerCase();
        if (e.key >= '1' && e.key <= '6') {
            const index = parseInt(e.key) - 1;
            if (cachedPockets[index]) {
                manualPocketOverride = index;
                const status = document.getElementById('active_pocket_status');
                if (status) status.innerHTML = `Foco: <span style="color:#00ffcc;">${cachedPockets[index].name}</span>`;
                draw();
            }
        }
        if (e.key === '0') {
            manualPocketOverride = -1;
            const status = document.getElementById('active_pocket_status');
            if (status) status.innerText = 'Modo: Todas as Caçapas';
            draw();
        }
        if (e.key === 'Insert' || key === 'm') {
            config.settingsPanelVisible = !config.settingsPanelVisible;
            settingsPanel.style.display = config.settingsPanelVisible ? 'block' : 'none';
            save();
        }
        if (key === 'h') { overlayCanvas.style.visibility = overlayCanvas.style.visibility === 'hidden' ? 'visible' : 'hidden'; }
        if (activeElement) {
            const step = e.shiftKey ? 5 : 1;
            let target = activeElement.type === 'cue' ? config.cueBallPos : config.calibPoints[activeElement.index];
            if (e.key === 'ArrowUp') target.y -= step;
            if (e.key === 'ArrowDown') target.y += step;
            if (e.key === 'ArrowLeft') target.x -= step;
            if (e.key === 'ArrowRight') target.x += step;
            if (e.key.startsWith('Arrow')) { e.preventDefault(); updateCachedPockets(); updateMarkersPosition(); draw(); save(); }
        }
    });

    // FIX 1: Use #game-iframe id as primary selector (CrazyGames sets src dynamically)
    // FIX 2: setInterval(draw, 100) not setInterval(draw(), 100)
    function init() {
        load();
        gameIframe = document.querySelector('#game-iframe') || document.querySelector('iframe[src*="8-ball-pool"]');
        if (!gameIframe) return setTimeout(init, 1000);
        updateCachedPockets(); createMarkers(); createPanel();
        window.addEventListener('resize', () => { updateMarkersPosition(); if (isPanningMode) syncPanBlockerSize(); draw(); });
        new ResizeObserver(() => { updateMarkersPosition(); if (isPanningMode) syncPanBlockerSize(); draw(); }).observe(gameIframe);
        setInterval(draw, 100); // FIX 2: was setInterval(draw(), 100)
        draw();
    }

    init();
})();