Greasy Fork is available in English.

Combat Mini-Game Bot

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

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==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 — стоп.');
})();