Автоматизация мини-игры на реакцию: определяет действие НПЦ и нажимает нужную кнопку
// ==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 — стоп.');
})();