Machu Jump - Auto Jumper

This userscript automatically performs jumps in the web game (https://bocchinet.com/GQux/).

// ==UserScript==
// @name         Machu Jump - Auto Jumper
// @version      0.1
// @license      GPLv3
// @description  This userscript automatically performs jumps in the web game (https://bocchinet.com/GQux/).
// @match        https://bocchinet.com/GQux/
// @resource     originalGameJS https://bocchinet.com/GQux/game.js
// @grant        GM_getResourceText
// @run-at       document-start
// @namespace https://greasyfork.org/users/1459377
// ==/UserScript==
 
(function() {
    'use strict';
    const SCRIPT_NAME = "Machu Auto-Jump Script";
    console.log(`${SCRIPT_NAME}: Initializing...`);
 
    // --- Configuration ---
    const TARGET_SCRIPT_FILENAME = 'game.js';
    const PHYSICS_LOG_INTERVAL = 30;
 
    // Auto-jump tuning parameters
    const PREDICT_BASE_VX = -5.0;
    const PREDICT_BASE_VY = -6.5;
    const HOLE_ATTRACTION = 2.5;
    const PREDICTION_SUCCESS_FACTOR = 0.8;
 
    let physicsUpdateLogCounter = 0;
 
    // Find and block the original game script
    const findAndBlockOriginalScript = () => {
        const scripts = document.getElementsByTagName('script');
        for (let script of scripts) {
            if (script.src && script.src.endsWith(TARGET_SCRIPT_FILENAME)) {
                console.log(`${SCRIPT_NAME}: Found target script tag.`);
                script.type = 'text/javascript-blocked';
                console.log(`${SCRIPT_NAME}: Tag blocked.`);
                return script;
            }
        }
        console.error(`${SCRIPT_NAME}: Target script tag not found.`);
        return null;
    };
 
    // Get the original game code
    const getOriginalCode = () => {
        try {
            // Try using GM_getResourceText first
            const code = GM_getResourceText('originalGameJS');
            if (code && code.trim() !== '') {
                console.log(`${SCRIPT_NAME}: Code obtained via GM_getResourceText.`);
                return Promise.resolve(code);
            }
            throw new Error("GM_getResourceText failed or returned empty result");
        } catch (e) {
            // Fall back to fetch
            console.log(`${SCRIPT_NAME}: Initiating fallback fetch...`);
            const gameJsUrl = new URL(TARGET_SCRIPT_FILENAME, window.location.href).href;
            return fetch(gameJsUrl)
                .then(response => {
                    if (!response.ok) throw new Error(`HTTP ${response.status}`);
                    return response.text();
                })
                .then(text => {
                    console.log(`${SCRIPT_NAME}: Code obtained via fallback fetch.`);
                    return text;
                })
                .catch(err => {
                    console.error(`${SCRIPT_NAME}: Fetch failed:`, err);
                    alert(`${SCRIPT_NAME} Error: Could not fetch game code`);
                    return null;
                });
        }
    };
 
    // Execute the modified code
    const executeCode = (codeToExecute) => {
        try {
            const scriptElement = document.createElement('script');
            scriptElement.id = 'machu-jump-modified-script';
            scriptElement.textContent = codeToExecute;
            (document.head || document.documentElement).appendChild(scriptElement);
            console.log(`${SCRIPT_NAME}: Modified script executed.`);
        } catch (e) {
            console.error(`${SCRIPT_NAME}: Execution error:`, e);
            alert(`${SCRIPT_NAME} Execution Error`);
        }
    };
 
    // Modify the game code and inject our auto-jump logic
    const modifyAndInject = (codeToModify) => {
        if (!codeToModify) {
            console.error(`${SCRIPT_NAME}: No code to modify.`);
            return;
        }
 
        console.log(`${SCRIPT_NAME}: Modifying code...`);
        let modifiedCode = codeToModify;
 
        // Find the updatePhysics function to inject our code
        const functionSignature = 'function updatePhysics() {';
        const functionIndex = modifiedCode.indexOf(functionSignature);
 
        if (functionIndex === -1) {
            console.error(`${SCRIPT_NAME}: updatePhysics function not found.`);
            executeCode(modifiedCode); // Execute unmodified code anyway
            return;
        }
 
        // Prepare the code to inject
        const injectionCode = `
            try {
                if (typeof physicsUpdateLogCounter === 'undefined') {
                    physicsUpdateLogCounter = 0;
                }
                physicsUpdateLogCounter++;
 
                // Predict if jump will succeed
                let shouldPredictJump = (
                    typeof gameState !== 'undefined' &&
                    gameState &&
                    gameState.ball &&
                    gameState.ball.isMoving &&
                    gameState.canJump &&
                    !gameState.hasJumped
                );
 
                let predictionResult = false;
                let finalSimDist = -1;
 
                if (shouldPredictJump) {
                    try {
                        // Initial simulation values
                        const startX = gameState.ball.x;
                        const startY = gameState.ball.y;
                        let simVX = ${PREDICT_BASE_VX};
                        let simVY = ${PREDICT_BASE_VY};
 
                        // Hole attraction calculation
                        const holeAttractionFactor = ${HOLE_ATTRACTION};
                        const toHoleX = gameState.hole.x - startX;
                        const toHoleY = gameState.hole.y - startY;
                        const distToHole = Math.sqrt(toHoleX * toHoleX + toHoleY * toHoleY);
 
                        if (distToHole > 0) {
                            simVX += (toHoleX / distToHole) * holeAttractionFactor;
                        }
 
                        // Simulation variables
                        let simX = startX;
                        let simY = startY;
                        const simGravity = 0.3;
                        const simFriction = 0.99;
                        const maxSteps = 60;
 
                        // Run simulation
                        for (let i = 0; i < maxSteps; i++) {
                            // Apply physics
                            simVY += simGravity;
                            simVX *= simFriction;
                            simVY *= simFriction;
                            simX += simVX;
                            simY += simVY;
 
                            // Calculate current distance to hole
                            const curDist = Math.sqrt(
                                Math.pow(simX - gameState.hole.x, 2) +
                                Math.pow(simY - gameState.hole.y, 2)
                            );
                            finalSimDist = curDist;
 
                            // Apply hole attraction in simulation
                            const attractionRadius = gameState.hole.radius * 4.0;
                            if (curDist < attractionRadius && curDist > 0) {
                                const holeVecX = gameState.hole.x - simX;
                                const holeVecY = gameState.hole.y - simY;
                                const pullFactor = 0.2 * (1 - curDist / attractionRadius);
                                simVX += (holeVecX / curDist) * pullFactor;
                                simVY += (holeVecY / curDist) * pullFactor;
                            }
 
                            // Check for success - requires getting close enough to hole
                            if (curDist < (gameState.hole.radius * ${PREDICTION_SUCCESS_FACTOR})) {
                                predictionResult = true;
                                break;
                            }
 
                            // Check for out of bounds (fail condition)
                            let ch = (typeof canvas !== 'undefined') ? canvas.height : 1000;
                            let cw = (typeof canvas !== 'undefined') ? canvas.width : 2000;
                            if (simY > ch || simX < 0 || simX > cw) {
                                predictionResult = false;
                                break;
                            }
                        }
                    } catch (predErr) {
                        console.error("Prediction error:", predErr);
                        predictionResult = false;
                    }
                }
 
                // Log state periodically
                if (physicsUpdateLogCounter % ${PHYSICS_LOG_INTERVAL} === 1) {
                    if (typeof gameState !== 'undefined' && gameState && gameState.ball) {
                        console.log(
                            'Frame=' + physicsUpdateLogCounter +
                            ' Moving=' + gameState.ball.isMoving +
                            ' CanJump=' + gameState.canJump +
                            ' HasJumped=' + gameState.hasJumped +
                            ' Predict=' + predictionResult +
                            ' (SimDist=' + (finalSimDist !== -1 ? finalSimDist.toFixed(2) : 'N/A') + ')'
                        );
                    } else {
                        console.log('Frame=' + physicsUpdateLogCounter + ', gameState not ready');
                    }
                }
 
                // Auto-jump logic
                if (predictionResult &&
                    typeof gameState !== 'undefined' &&
                    gameState &&
                    !gameState.isGameOver &&
                    !gameState.isTimeUp &&
                    !gameState.showTitleScreen &&
                    gameState.ball &&
                    gameState.ball.isMoving &&
                    gameState.canJump &&
                    !gameState.hasJumped &&
                    typeof performJump === 'function')
                {
                    console.log("Auto-Jump triggered: Prediction=" + predictionResult);
                    performJump();
                }
            } catch (e) {
                console.error("Error in injected code:", e);
            }
        `;
 
        // Inject the code at the beginning of updatePhysics
        const injectionPoint = functionIndex + functionSignature.length;
        modifiedCode = modifiedCode.slice(0, injectionPoint) + injectionCode + modifiedCode.slice(injectionPoint);
        console.log(`${SCRIPT_NAME}: Code modification complete.`);
 
        executeCode(modifiedCode);
    };
 
    const originalScriptTag = findAndBlockOriginalScript();
    if (originalScriptTag) {
        getOriginalCode().then(code => {
            if (code) {
                modifyAndInject(code);
            } else {
                console.error(`${SCRIPT_NAME}: Failed to get original code.`);
            }
        });
    } else {
        console.log(`${SCRIPT_NAME}: Script won't execute as target was not found.`);
    }
})();