Combat Mini-Game Bot

Автоматизация мини-игры на реакцию: определяет действие НПЦ и нажимает нужную кнопку

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name         Combat Mini-Game Bot
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Автоматизация мини-игры на реакцию: определяет действие НПЦ и нажимает нужную кнопку
// @match        https://www.crazygames.com/game/llama-legends/*
// @match        https://llamalegends.com/*
// @match        https://igre.games/en/llama-legends/*
// @grant        none
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    // ─── Параметры ───────────────────────────────────────────────────────────
    const POLL_MS             = 50;   // интервал опроса DOM
    const LOSE_TIMEOUT_MS     = 3000; // ждём проигрыша по таймауту на 51-м раунде
    const TRAIN_AGAIN_DELAY_MS = 2000; // пауза перед нажатием "Train Again"
    const MAX_SCORING_ROUNDS  = 50;   // раунды 1–50 приносят очки

    // ─── Машина состояний ────────────────────────────────────────────────────
    // Возможные состояния:
    //   'idle'          — бот не запущен
    //   'wait_npc'      — ждём появления действия НПЦ (фон ещё отсутствует)
    //   'wait_buttons'  — НПЦ обнаружен, ждём активации наших кнопок
    //   'wait_npc_gone' — кнопка нажата, ждём исчезновения фона НПЦ (смена раунда)
    //   'lose_round'    — 51-й раунд, намеренно ничего не нажимаем
    //   'wait_train'    — ждём появления кнопки "Train Again"
    let state       = 'idle';
    let roundCount  = 0;
    let npcAction   = null;  // действие НПЦ в текущем раунде ('red'/'blue'/'yellow')
    let pollInterval = null;


    function simulateClick(el) {
    if (!el) return;

    const rect = el.getBoundingClientRect();
    const x = rect.left + rect.width / 2;
    const y = rect.top + rect.height / 2;

    ['pointerdown', 'mousedown', 'pointerup', 'mouseup', 'click'].forEach(type => {
        el.dispatchEvent(new MouseEvent(type, {
            view: window,
            bubbles: true,
            cancelable: true,
            clientX: x,
            clientY: y,
            button: 0
        }));
    });
}

    // ─── Определение действия НПЦ ────────────────────────────────────────────
    function detectNpcAction() {
        if (document.querySelector('.bg-red-500.blur-2xl'))    return 'red';
        if (document.querySelector('.bg-blue-500.blur-2xl'))   return 'blue';
        if (document.querySelector('.bg-yellow-500.blur-2xl')) return 'yellow';
        return null;
    }

    // ─── Поиск кнопки по тексту ──────────────────────────────────────────────
    function findButton(text) {
        return [...document.querySelectorAll('button')].find(
            b => b.textContent.trim() === text
        ) || null;
    }

    // ─── Проверка: кнопка существует и активна ───────────────────────────────
    function isEnabled(btn) {
        if (!btn) return false;
        if (btn.disabled) return false;
        if (window.getComputedStyle(btn).pointerEvents === 'none') return false;
        return true;
    }

    // ─── Выбор нужной кнопки по действию НПЦ ─────────────────────────────────
    // красный (атака НПЦ) → Block!
    // синий   (блок НПЦ)  → Attack!
    // жёлтый  (прыжок)    → Spit!
    function getResponseButton(action) {
        switch (action) {
            case 'red':    return findButton('Block!');
            case 'blue':   return findButton('Attack!');
            case 'yellow': return findButton('Spit!');
            default:       return null;
        }
    }

    // ─── Основной тик ────────────────────────────────────────────────────────
    function tick() {
        if (state === 'idle') return;

        switch (state) {

            // Ждём появления действия НПЦ
            case 'wait_npc': {
                const action = detectNpcAction();
                if (!action) return; // фона ещё нет — ждём

                roundCount++;
                npcAction = action;
                log(`Раунд ${roundCount} начался, НПЦ: ${action}`);

                // 51-й раунд — намеренно проигрываем
                if (roundCount > MAX_SCORING_ROUNDS) {
                    log(`Раунд ${roundCount} > ${MAX_SCORING_ROUNDS}, ждём таймаута (${LOSE_TIMEOUT_MS} мс)...`);
                    state = 'lose_round';
                    stopPoll();
                    setTimeout(() => {
                        if (state !== 'lose_round') return;
                        log('Раунд проигран по таймауту, ищем "Train Again"...');
                        state = 'wait_train';
                        startPoll();
                    }, LOSE_TIMEOUT_MS);
                    return;
                }

                // Нормальный раунд — ждём активации кнопок
                state = 'wait_buttons';
                return;
            }

            // Ждём, пока кнопка станет активной, и кликаем
            case 'wait_buttons': {
                const btn = getResponseButton(npcAction);
                if (!isEnabled(btn)) return; // кнопка ещё серая — ждём

                simulateClick(btn);
                log(`Раунд ${roundCount}: НПЦ — ${npcAction}, нажали "${btn.textContent.trim()}"`);
                state = 'wait_npc_gone';
                return;
            }

            // Ждём, пока фон НПЦ исчезнет — это сигнал о начале нового раунда
            case 'wait_npc_gone': {
                const action = detectNpcAction();
                if (action !== null) return; // фон ещё виден — ждём

                // Фон исчез: сервер принял ответ, сейчас пойдёт новый раунд
                npcAction = null;
                state = 'wait_npc';
                return;
            }

            // Ждём кнопку "Train Again"
            case 'wait_train': {
                const trainBtn = [...document.querySelectorAll('button')].find(
                    b => b.textContent.trim() === 'Train Again'
                );
                if (!trainBtn) return; // кнопка ещё не появилась

                log(`Найдена кнопка "Train Again", нажмём через ${TRAIN_AGAIN_DELAY_MS} мс...`);
                state = 'idle'; // временно гасим, чтобы повторно не сработало
                stopPoll();
                setTimeout(() => {
                    simulateClick(trainBtn);
                    log('Нажали "Train Again", начинаем новый цикл');
                    roundCount = 0;
                    npcAction  = null;
                    state      = 'wait_npc';
                    startPoll();
                }, TRAIN_AGAIN_DELAY_MS);
                return;
            }
        }
    }

    // ─── Управление интервалом ────────────────────────────────────────────────
    function startPoll() {
        if (pollInterval) return;
        pollInterval = setInterval(tick, POLL_MS);
    }

    function stopPoll() {
        if (pollInterval) {
            clearInterval(pollInterval);
            pollInterval = null;
        }
    }

    // ─── Старт и стоп ────────────────────────────────────────────────────────
    function startBot() {
        if (state !== 'idle') {
            log('Бот уже запущен!');
            return;
        }
        roundCount = 0;
        npcAction  = null;
        state      = 'wait_npc';
        showOverlay();
        log('Бот запущен. Alt+Shift+X — остановить.');
        startPoll();
    }

    function stopBot() {
        if (state === 'idle') return;
        state = 'idle';
        stopPoll();
        hideOverlay();
        log('Бот остановлен.');
    }

    // ─── Оверлей ─────────────────────────────────────────────────────────────
    let overlay = null;

    function showOverlay() {
        overlay = document.createElement('div');
        overlay.id = '__combat_bot_overlay';
        overlay.style.cssText = `
            position: fixed;
            top: 12px;
            right: 12px;
            z-index: 999999;
            background: rgba(15,15,20,0.82);
            color: #e2e8f0;
            font: 13px/1.6 monospace;
            padding: 8px 14px;
            border-radius: 8px;
            pointer-events: none;
            min-width: 180px;
            backdrop-filter: blur(4px);
            border: 1px solid rgba(255,255,255,0.1);
        `;
        overlay.innerHTML = `
            <b style="color:#6ee7b7">▶ BOT ACTIVE</b><br>
            Раунд: <span id="__bot_rounds">0</span> / ${MAX_SCORING_ROUNDS}<br>
            <span id="__bot_state" style="color:#94a3b8">wait_npc</span>
        `;
        document.body.appendChild(overlay);
    }

    function hideOverlay() {
        if (overlay) { overlay.remove(); overlay = null; }
    }

    function updateOverlay() {
        const r = document.getElementById('__bot_rounds');
        const s = document.getElementById('__bot_state');
        if (r) r.textContent = roundCount;
        if (s) s.textContent = state;
    }

    // ─── Логгер ───────────────────────────────────────────────────────────────
    function log(msg) {
        console.log(`[CombatBot] ${msg}`);
        updateOverlay();
    }

    // ─── Шорткаты ─────────────────────────────────────────────────────────────
    // Alt+Shift+F — запуск
    // Alt+Shift+X — остановка
    document.addEventListener('keydown', function (e) {
        if (e.altKey && e.shiftKey && e.code === 'KeyF') {
            e.preventDefault();
            startBot();
        }
        if (e.altKey && e.shiftKey && e.code === 'KeyX') {
            e.preventDefault();
            stopBot();
        }
    });

    console.log('[CombatBot] Скрипт загружен. Alt+Shift+F — старт, Alt+Shift+X — стоп.');
})();