// ==UserScript==
// @name 8 Ball Helper for CrazyGames
// @namespace http://tampermonkey.net/
// @version 2.1.0
// @description Cheat for 8 Ball Pool on CrazyGames with manual calibration and correct alignment.
// @author Adapted by Mazurka
// @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 = 'Advanced8BallPoolGuide_v2_1_0';
// Default config with calibration points roughly around the iframe center
let config = {
calibPoints: [
{ x: 50, y: 50 }, // Top-left corner of game area inside iframe
{ x: 750, y: 50 }, // Top-right
{ x: 750, y: 450 }, // Bottom-right
{ x: 50, y: 450 } // Bottom-left
],
cueBallPos: { x: 400, y: 300 }, // Initial cue ball position inside iframe
lineWidth: 2,
lineColor: '#FFFFFF',
guideLineColor: 'rgba(200, 200, 200, 0.6)',
calibPointMarkerColor: 'rgba(0, 255, 0, 0.5)',
calibPointSize: 30,
cueBallSize: 15,
calibrationLocked: false,
settingsPanelVisible: true,
settingsPanelPos: { top: '10px', left: '10px' }
};
let gameIframe = null;
let overlayCanvas = null;
let ctx = null;
let settingsPanel = null;
let cueBallMarker = null;
let calibMarkers = [];
let isDraggingCueBall = false;
let isDraggingCalibPoint = false;
let activeCalibIndex = -1;
let isDraggingPanel = false;
let dragStartMouse = { x: 0, y: 0 };
let dragStartPos = { x: 0, y: 0 };
let drawRequested = false;
// Calculate pockets based on calibration points
function calculatePockets() {
if (config.calibPoints.length !== 4) return [];
const [tl, tr, br, bl] = config.calibPoints;
let pockets = [];
pockets.push({ x: tl.x, y: tl.y, name: 'Top-Left' });
pockets.push({ x: tr.x, y: tr.y, name: 'Top-Right' });
pockets.push({ x: br.x, y: br.y, name: 'Bottom-Right' });
pockets.push({ x: bl.x, y: bl.y, name: 'Bottom-Left' });
// Mid pockets on top and bottom cushions
pockets.push({ x: (tl.x + tr.x) / 2, y: (tl.y + tr.y) / 2, name: 'Top-Mid' });
pockets.push({ x: (bl.x + br.x) / 2, y: (bl.y + br.y) / 2, name: 'Bottom-Mid' });
return pockets;
}
// Save config persistently
function saveSettings() {
GM_setValue(SCRIPT_ID + '_settings', JSON.stringify(config));
}
// Load config from storage
function loadSettings() {
const saved = GM_getValue(SCRIPT_ID + '_settings');
if (saved) {
try {
const loaded = JSON.parse(saved);
for (const key in config) {
if (loaded[key] !== undefined) {
config[key] = loaded[key];
}
}
} catch (e) {
console.warn(SCRIPT_ID + ': Failed to parse saved settings', e);
}
}
}
// Find the game iframe by matching its src URL pattern
function getGameIframe() {
const iframes = document.querySelectorAll('iframe');
for (const iframe of iframes) {
if (iframe.src && iframe.src.includes('8-ball-pool-billiards-multiplayer')) {
return iframe;
}
}
return null;
}
// Create the settings panel UI
function createSettingsPanel() {
settingsPanel = document.createElement('div');
settingsPanel.id = SCRIPT_ID + '_SettingsPanel';
settingsPanel.style.position = 'fixed';
settingsPanel.style.top = config.settingsPanelPos.top;
settingsPanel.style.left = config.settingsPanelPos.left;
settingsPanel.style.backgroundColor = 'rgba(50, 50, 50, 0.9)';
settingsPanel.style.color = 'white';
settingsPanel.style.padding = '10px';
settingsPanel.style.borderRadius = '8px';
settingsPanel.style.zIndex = 10002;
settingsPanel.style.minWidth = '250px';
settingsPanel.style.fontFamily = 'Arial, sans-serif';
settingsPanel.style.userSelect = 'none';
settingsPanel.style.display = config.settingsPanelVisible ? 'block' : 'none';
settingsPanel.innerHTML = `
<div id="${SCRIPT_ID}_PanelHeader" style="cursor: grab; font-weight: bold; margin-bottom: 8px; text-align: center;">8 Ball Pool Guide (Toggle: Insert)</div>
<label>Line Width: <span id="${SCRIPT_ID}_lineWidthValue">${config.lineWidth}</span> px</label>
<input type="range" id="${SCRIPT_ID}_lineWidth" min="1" max="10" value="${config.lineWidth}" style="width: 100%;">
<label>Line Color:</label>
<input type="color" id="${SCRIPT_ID}_lineColor" value="${config.lineColor}" style="width: 100%;">
<label><input type="checkbox" id="${SCRIPT_ID}_calibrationLocked" ${config.calibrationLocked ? 'checked' : ''}> Lock Calibration Points</label>
<button id="${SCRIPT_ID}_saveSettings" style="width: 100%; margin-top: 10px;">Save Settings</button>
<button id="${SCRIPT_ID}_resetCalibration" style="width: 100%; margin-top: 5px;">Reset Calibration</button>
<div style="font-size: 12px; margin-top: 10px; color: #ccc;">Drag green points to calibrate corners.<br>Drag red circle to move cue ball.</div>
`;
document.body.appendChild(settingsPanel);
// Event listeners
document.getElementById(`${SCRIPT_ID}_lineWidth`).addEventListener('input', e => {
config.lineWidth = parseInt(e.target.value);
document.getElementById(`${SCRIPT_ID}_lineWidthValue`).textContent = config.lineWidth;
requestDraw();
});
document.getElementById(`${SCRIPT_ID}_lineColor`).addEventListener('input', e => {
config.lineColor = e.target.value;
requestDraw();
});
document.getElementById(`${SCRIPT_ID}_calibrationLocked`).addEventListener('change', e => {
config.calibrationLocked = e.target.checked;
updateMarkersVisibility();
saveSettings();
});
document.getElementById(`${SCRIPT_ID}_saveSettings`).addEventListener('click', () => {
saveSettings();
alert('Settings saved!');
});
document.getElementById(`${SCRIPT_ID}_resetCalibration`).addEventListener('click', () => {
resetCalibrationPoints();
saveSettings();
requestDraw();
});
// Drag panel
const header = document.getElementById(`${SCRIPT_ID}_PanelHeader`);
header.addEventListener('mousedown', e => {
isDraggingPanel = true;
dragStartMouse = { x: e.clientX, y: e.clientY };
dragStartPos = { x: settingsPanel.offsetLeft, y: settingsPanel.offsetTop };
header.style.cursor = 'grabbing';
document.body.style.userSelect = 'none';
});
window.addEventListener('mouseup', () => {
if (isDraggingPanel) {
isDraggingPanel = false;
header.style.cursor = 'grab';
document.body.style.userSelect = '';
config.settingsPanelPos = { top: settingsPanel.style.top, left: settingsPanel.style.left };
saveSettings();
}
});
window.addEventListener('mousemove', e => {
if (isDraggingPanel) {
const dx = e.clientX - dragStartMouse.x;
const dy = e.clientY - dragStartMouse.y;
settingsPanel.style.left = dragStartPos.x + dx + 'px';
settingsPanel.style.top = dragStartPos.y + dy + 'px';
}
});
// Toggle panel visibility with Insert key
window.addEventListener('keydown', e => {
if (e.key === 'Insert') {
config.settingsPanelVisible = !config.settingsPanelVisible;
settingsPanel.style.display = config.settingsPanelVisible ? 'block' : 'none';
saveSettings();
}
});
}
// Reset calibration points to default
function resetCalibrationPoints() {
config.calibPoints = [
{ x: 50, y: 50 },
{ x: 750, y: 50 },
{ x: 750, y: 450 },
{ x: 50, y: 450 }
];
config.cueBallPos = { x: 400, y: 300 };
updateMarkersPosition();
}
// Create overlay canvas and draggable markers
function createOverlayAndMarkers() {
// Create overlay canvas
overlayCanvas = document.createElement('canvas');
overlayCanvas.id = SCRIPT_ID + '_Overlay';
overlayCanvas.style.position = 'absolute';
overlayCanvas.style.top = '0';
overlayCanvas.style.left = '0';
overlayCanvas.style.zIndex = 10000;
overlayCanvas.style.pointerEvents = 'none'; // Let mouse events pass through
document.body.appendChild(overlayCanvas);
ctx = overlayCanvas.getContext('2d');
// Create cue ball marker (red circle)
cueBallMarker = document.createElement('div');
cueBallMarker.id = SCRIPT_ID + '_CueBallMarker';
cueBallMarker.style.position = 'absolute';
cueBallMarker.style.width = config.cueBallSize * 2 + 'px';
cueBallMarker.style.height = config.cueBallSize * 2 + 'px';
cueBallMarker.style.border = '3px solid red';
cueBallMarker.style.borderRadius = '50%';
cueBallMarker.style.backgroundColor = 'rgba(255,0,0,0.3)';
cueBallMarker.style.cursor = 'grab';
cueBallMarker.style.zIndex = 10001;
document.body.appendChild(cueBallMarker);
cueBallMarker.addEventListener('mousedown', e => {
e.stopPropagation();
isDraggingCueBall = true;
dragStartMouse = { x: e.clientX, y: e.clientY };
dragStartPos = { ...config.cueBallPos };
cueBallMarker.style.cursor = 'grabbing';
document.body.style.userSelect = 'none';
});
// Create calibration point markers (green squares)
calibMarkers = [];
for (let i = 0; i < 4; i++) {
const marker = document.createElement('div');
marker.className = SCRIPT_ID + '_CalibMarker';
marker.style.position = 'absolute';
marker.style.width = config.calibPointSize + 'px';
marker.style.height = config.calibPointSize + 'px';
marker.style.backgroundColor = config.calibPointMarkerColor;
marker.style.borderRadius = '6px';
marker.style.cursor = 'grab';
marker.style.zIndex = 10001;
marker.dataset.index = i;
document.body.appendChild(marker);
marker.addEventListener('mousedown', e => {
e.stopPropagation();
if (config.calibrationLocked) return;
isDraggingCalibPoint = true;
activeCalibIndex = parseInt(marker.dataset.index);
dragStartMouse = { x: e.clientX, y: e.clientY };
dragStartPos = { ...config.calibPoints[activeCalibIndex] };
marker.style.cursor = 'grabbing';
document.body.style.userSelect = 'none';
});
calibMarkers.push(marker);
}
updateMarkersVisibility();
updateMarkersPosition();
}
// Update visibility of calibration markers based on lock state
function updateMarkersVisibility() {
calibMarkers.forEach(marker => {
marker.style.display = config.calibrationLocked ? 'none' : 'block';
marker.style.cursor = config.calibrationLocked ? 'default' : 'grab';
});
}
// Update position of all markers relative to iframe
function updateMarkersPosition() {
if (!gameIframe) return;
const rect = gameIframe.getBoundingClientRect();
// Position cue ball marker
cueBallMarker.style.left = rect.left + config.cueBallPos.x - config.cueBallSize + 'px';
cueBallMarker.style.top = rect.top + config.cueBallPos.y - config.cueBallSize + 'px';
// Position calibration markers
calibMarkers.forEach((marker, i) => {
const p = config.calibPoints[i];
marker.style.left = rect.left + p.x - config.calibPointSize / 2 + 'px';
marker.style.top = rect.top + p.y - config.calibPointSize / 2 + 'px';
});
}
// Update overlay canvas size and position to match iframe
function updateOverlaySizeAndPosition() {
if (!gameIframe || !overlayCanvas) return;
const rect = gameIframe.getBoundingClientRect();
overlayCanvas.style.left = rect.left + 'px';
overlayCanvas.style.top = rect.top + 'px';
overlayCanvas.width = rect.width;
overlayCanvas.height = rect.height;
updateMarkersPosition();
requestDraw();
}
// Request a redraw on next animation frame
function requestDraw() {
if (!drawRequested) {
drawRequested = true;
requestAnimationFrame(() => {
draw();
drawRequested = false;
});
}
}
// Draw guide lines and calibration grid
function draw() {
if (!ctx || !overlayCanvas) return;
ctx.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
// Draw calibration grid lines if unlocked
if (!config.calibrationLocked && config.calibPoints.length === 4) {
const [tl, tr, br, bl] = config.calibPoints;
ctx.strokeStyle = config.guideLineColor;
ctx.lineWidth = Math.max(1, config.lineWidth / 2);
ctx.beginPath();
ctx.moveTo(tl.x, tl.y);
ctx.lineTo(tr.x, tr.y);
ctx.lineTo(br.x, br.y);
ctx.lineTo(bl.x, bl.y);
ctx.closePath();
ctx.moveTo(tl.x, tl.y);
ctx.lineTo(br.x, br.y);
ctx.moveTo(tr.x, tr.y);
ctx.lineTo(bl.x, bl.y);
ctx.stroke();
}
// Draw lines from cue ball to pockets
const pockets = calculatePockets();
ctx.strokeStyle = config.lineColor;
ctx.lineWidth = config.lineWidth;
pockets.forEach(pocket => {
ctx.beginPath();
ctx.moveTo(config.cueBallPos.x, config.cueBallPos.y);
ctx.lineTo(pocket.x, pocket.y);
ctx.stroke();
});
}
// Handle mouse move for dragging markers
function onDocumentMouseMove(e) {
if (!gameIframe) return;
if (!isDraggingCueBall && !isDraggingCalibPoint && !isDraggingPanel) return;
const rect = gameIframe.getBoundingClientRect();
const mouseXInIframe = e.clientX - rect.left;
const mouseYInIframe = e.clientY - rect.top;
if (isDraggingPanel) {
// Handled in panel drag listeners
return;
}
if (isDraggingCueBall) {
const dx = mouseXInIframe - (dragStartMouse.x - rect.left);
const dy = mouseYInIframe - (dragStartMouse.y - rect.top);
config.cueBallPos.x = dragStartPos.x + dx;
config.cueBallPos.y = dragStartPos.y + dy;
clampCueBallPosition();
updateMarkersPosition();
requestDraw();
} else if (isDraggingCalibPoint && activeCalibIndex !== -1) {
const dx = mouseXInIframe - (dragStartMouse.x - rect.left);
const dy = mouseYInIframe - (dragStartMouse.y - rect.top);
config.calibPoints[activeCalibIndex].x = dragStartPos.x + dx;
config.calibPoints[activeCalibIndex].y = dragStartPos.y + dy;
clampCalibrationPoint(activeCalibIndex);
updateMarkersPosition();
requestDraw();
}
}
// Clamp cue ball position inside iframe bounds
function clampCueBallPosition() {
if (!gameIframe) return;
const rect = gameIframe.getBoundingClientRect();
config.cueBallPos.x = Math.min(Math.max(config.cueBallPos.x, 0), rect.width);
config.cueBallPos.y = Math.min(Math.max(config.cueBallPos.y, 0), rect.height);
}
// Clamp calibration points inside iframe bounds
function clampCalibrationPoint(index) {
if (!gameIframe) return;
const rect = gameIframe.getBoundingClientRect();
let p = config.calibPoints[index];
p.x = Math.min(Math.max(p.x, 0), rect.width);
p.y = Math.min(Math.max(p.y, 0), rect.height);
}
// Handle mouse up to stop dragging
function onDocumentMouseUp(e) {
if (isDraggingCueBall) {
isDraggingCueBall = false;
cueBallMarker.style.cursor = 'grab';
document.body.style.userSelect = '';
saveSettings();
}
if (isDraggingCalibPoint) {
isDraggingCalibPoint = false;
if (activeCalibIndex !== -1) {
calibMarkers[activeCalibIndex].style.cursor = 'grab';
}
activeCalibIndex = -1;
document.body.style.userSelect= '';
saveSettings();
}
}
// Initialize everything after iframe is ready
function init() {
console.log(SCRIPT_ID + ': Initializing...');
loadSettings();
gameIframe = getGameIframe();
if (!gameIframe) {
console.warn(SCRIPT_ID + ': Game iframe not found.');
return;
}
createOverlayAndMarkers();
createSettingsPanel();
updateOverlaySizeAndPosition();
requestDraw();
window.addEventListener('resize', updateOverlaySizeAndPosition);
window.addEventListener('scroll', updateOverlaySizeAndPosition);
document.addEventListener('mousemove', onDocumentMouseMove);
document.addEventListener('mouseup', onDocumentMouseUp);
console.log(SCRIPT_ID + ': Initialization complete.');
}
// Wait for iframe to be available and loaded
function waitForIframeAndInit() {
gameIframe = getGameIframe();
if (gameIframe && gameIframe.contentWindow) {
// Wait for iframe to load fully
if (gameIframe.contentDocument && gameIframe.contentDocument.readyState === 'complete') {
init();
} else {
gameIframe.addEventListener('load', () => {
init();
});
}
} else {
console.log(SCRIPT_ID + ': Waiting for game iframe...');
setTimeout(waitForIframeAndInit, 500);
}
}
waitForIframeAndInit();
})();