8 Ball Cheat

Cheat for any 8 ball pool game!

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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();
})();