Combat Mini-Game Bot

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

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

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

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

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

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

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

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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 — стоп.');
})();