FarmGod vollstĂ€ndig integriert â keine externe AbhĂ€ngigkeit
Acest script nu ar trebui instalat direct. Aceasta este o bibliotecÄ pentru alte scripturi care este inclusÄ prin directiva meta a // @require https://update.greasyfork.org/scripts/587597/1880936/%F0%9F%92%AA%20Anaboles%20farmen.js
// ==UserScript==
// @name đȘ Anaboles farmen
// @version 25
// @include https://*/game.php*screen=am_farm*
// @namespace https://greasyfork.org/users/1388863
// @description FarmGod vollstĂ€ndig integriert â keine externe AbhĂ€ngigkeit
// @grant unsafeWindow
// ==/UserScript==
(function () {
'use strict';
// Hub-Check: respektiert den Ein/Aus-Schalter im IntelliScripts Hub (id: 'autofarm')
function isEnabledInHub() {
try {
const reg = JSON.parse(localStorage.getItem('intellifarm_registry') || '{}');
return reg.autofarm !== false; // Default: aktiviert, falls Hub fehlt/Eintrag fehlt
} catch { return true; }
}
if (!isEnabledInHub()) {
return;
}
// Screen-Guard: lÀuft jetzt evtl. per @require unter dem breiteren @match des Hubs,
// daher muss die ursprĂŒngliche @include-EinschrĂ€nkung (screen=am_farm) hier im Code selbst geprĂŒft werden.
if (typeof game_data === 'undefined' || game_data.screen !== 'am_farm') {
return;
}
const domain = window.location.hostname.split('.')[0];
const DEFAULTS = {
enterMin: 250, enterMax: 500,
reloadMin: 600, reloadMax: 900,
webhookUrl: '',
holdEnter: false, holdInterval: 50,
optionGroup: 0, optionDistance: 25,
optionTime: 10, optionLosses: false,
optionMaxloot: true, optionNewbarbs: true,
villageDistances: {},
villageSettings: {},
// Nachtmodus
nightModeEnabled: false,
nightStart: '23:00',
nightEnd: '07:00',
nightReloadMin: 300,
nightReloadMax: 600,
nightOptionTime: 20,
nightOptionMaxloot: true,
nightOptionLosses: false,
// ZufÀllige Pausen
randomPauseEnabled: false,
randomPauseIntervalMin: 20,
randomPauseIntervalMax: 40,
randomPauseDurationMin: 5,
randomPauseDurationMax: 15,
kattaEnabled: false,
kattaGroup: 0,
kattaAxe: 0,
kattaRam: 0,
kattaSpy: 0,
kattaCata: 1,
kattaTarget: 'wall',
kattaOnRed: true,
kattaOnYellow: false,
};
function loadSettings() {
try {
const saved = JSON.parse(localStorage.getItem(domain + '_autofarm_settings') || '{}');
const merged = Object.assign({}, DEFAULTS, saved);
// Objekt-Felder explizit mergen damit sie nicht von DEFAULTS ĂŒberschrieben werden
merged.villageSettings = saved.villageSettings || {};
merged.villageDistances = saved.villageDistances || {};
return merged;
}
catch (e) { return Object.assign({}, DEFAULTS); }
}
function saveSettings(s) { localStorage.setItem(domain + '_autofarm_settings', JSON.stringify(s)); }
function loadPosition() { try { return JSON.parse(localStorage.getItem(domain + '_autofarm_pos') || 'null'); } catch (e) { return null; } }
function savePosition(x, y) { localStorage.setItem(domain + '_autofarm_pos', JSON.stringify({ x, y })); }
function loadMinimized() { return localStorage.getItem(domain + '_autofarm_minimized') === 'true'; }
function saveMinimized(val) { localStorage.setItem(domain + '_autofarm_minimized', val); }
let settings = loadSettings();
let isRunning = JSON.parse(localStorage.getItem(domain + '_isRunning')) || false;
let isMinimized = loadMinimized();
let intervalId, holdIntervalId, countdownInterval, hourlyAlertInterval;
let emptyFarmChecks = 0;
let captchaAlertSent = localStorage.getItem(domain + '_captcha_paused') === 'true';
let currentStatus = 'Bereit', currentCountdownText = '';
let holdRepeatCount = 0;
let todayLoot = null;
let todayVillages = null;
const EMPTY_FARM_THRESHOLD = 4;
function randomDelay(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }
// =====================================================================
// DISCORD
// =====================================================================
function sendDiscordCaptchaAlert() {
settings = loadSettings();
if (!settings.webhookUrl) return;
fetch(settings.webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: 'Anaboles farmen',
embeds: [{
title: 'â ïž Captcha erkannt!',
description: 'Anaboles farmen wurde gestoppt.',
color: 0xe74c3c,
fields: [
{ name: 'đ Server', value: domain, inline: true },
{ name: 'đ Zeit', value: new Date().toLocaleString('de-DE'), inline: true },
{ name: 'đ Seite', value: window.location.href, inline: false }
],
footer: { text: 'Anaboles farmen â Captcha Alert' }
}]
})
}).catch(e => console.error('[Autofarm] Webhook Fehler:', e));
}
function sendDiscordHourlyUpdate() {
settings = loadSettings();
if (!settings.webhookUrl) return;
const avg = (todayLoot && todayVillages && todayVillages.value > 0)
? formatLoot(Math.round(todayLoot.value / todayVillages.value)) + ' / Angriff'
: 'â';
fetch(settings.webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: 'Anaboles farmen',
embeds: [{
title: 'đ StĂŒndliches Update',
description: `Anaboles farmen lÀuft auf **${domain}**`,
color: 0x27ae60,
fields: [
{ name: 'đŠ Rohstoffe heute', value: todayLoot ? `${todayLoot.value.toLocaleString('de-DE')} (Rang ${todayLoot.rank})` : 'â', inline: false },
{ name: 'đïž Dörfer heute', value: todayVillages ? `${todayVillages.value.toLocaleString('de-DE')} (Rang ${todayVillages.rank})` : 'â', inline: false },
{ name: '⥠Schnitt', value: avg, inline: false },
{ name: 'đ Zeit', value: new Date().toLocaleString('de-DE'), inline: true },
{ name: 'âïž Status', value: isRunning ? 'â
LĂ€uft' : 'âč Gestoppt', inline: true }
],
footer: { text: 'Anaboles farmen â StĂŒndliches Update' }
}]
})
}).catch(e => console.error('[Autofarm] Webhook Fehler:', e));
}
const HOURLY_ALERT_KEY = domain + '_autofarm_last_hourly_alert';
function checkAndSendHourlyAlert() {
if (!isRunning) return;
if (!loadSettings().webhookUrl) return;
const last = parseInt(localStorage.getItem(HOURLY_ALERT_KEY) || '0');
const now = Date.now();
if (now - last >= 60 * 60 * 1000) {
localStorage.setItem(HOURLY_ALERT_KEY, now);
// Daten laden, dann erst senden wenn Daten wirklich da sind
fetchTodayLoot(sendDiscordHourlyUpdate);
}
}
function startHourlyAlert() {
clearInterval(hourlyAlertInterval);
// Timestamp nur setzen wenn noch keiner vorhanden (ĂŒberlebt Reloads)
if (!localStorage.getItem(HOURLY_ALERT_KEY)) {
localStorage.setItem(HOURLY_ALERT_KEY, Date.now());
}
// Jede Minute prĂŒfen ob eine Stunde vergangen ist (reload-sicher)
hourlyAlertInterval = setInterval(checkAndSendHourlyAlert, 60 * 1000);
}
function stopHourlyAlert() {
clearInterval(hourlyAlertInterval);
// Timestamp zurĂŒcksetzen damit beim nĂ€chsten Start neu gezĂ€hlt wird
localStorage.removeItem(HOURLY_ALERT_KEY);
}
// =====================================================================
// TAGESERGEBNIS
// =====================================================================
function parseRankingPage(html) {
const $html = $(html);
const bodyText = $html.text ? $html.text() : $(html).text();
const matchDE = bodyText.match(/Mein heutiges Ergebnis[^\d]*([\d\.]+)/i);
const matchEN = bodyText.match(/My result today[^\d]*([\d,]+)/i);
const matchRaw = matchDE || matchEN;
let value = null;
if (matchRaw) value = parseInt(matchRaw[1].replace(/\./g, '').replace(/,/g, ''));
let rank = null;
$html.find('tr').each((i, row) => {
const $row = $(row);
if ($row.find('b, strong').length > 0) {
const cells = $row.find('td');
if (cells.length >= 2) {
const r = parseInt(cells.eq(0).text().trim());
if (!isNaN(r) && r > 0) rank = r;
}
}
});
return { value, rank };
}
function fetchTodayLoot(callback) {
const urlRes = TribalWars.buildURL('GET', 'ranking', { mode: 'in_a_day', type: 'loot_res' });
const urlVil = TribalWars.buildURL('GET', 'ranking', { mode: 'in_a_day', type: 'loot_vil' });
Promise.all([$.get(urlRes), $.get(urlVil)]).then(([htmlRes, htmlVil]) => {
const res = parseRankingPage(htmlRes);
const vil = parseRankingPage(htmlVil);
if (res.value && !isNaN(res.value)) todayLoot = { value: res.value, rank: res.rank || '?', updated: new Date() };
if (vil.value && !isNaN(vil.value)) todayVillages = { value: vil.value, rank: vil.rank || '?', updated: new Date() };
updateLootDisplay();
console.log(`[Autofarm] Rohstoffe: ${res.value} | Dörfer: ${vil.value}`);
if (typeof callback === 'function') callback();
}).catch(e => console.warn('[Autofarm] Loot-Abruf fehlgeschlagen:', e));
}
function formatLoot(val) {
if (!val || isNaN(val)) return 'â';
if (val >= 1000000) return (val / 1000000).toFixed(1) + 'M';
if (val >= 1000) return (val / 1000).toFixed(1) + 'K';
return val.toLocaleString('de-DE');
}
function updateLootDisplay() {
const mini = document.getElementById('af-loot-mini');
const fullRes = document.getElementById('af-loot-res');
const fullVil = document.getElementById('af-loot-vil');
const fullAvg = document.getElementById('af-loot-avg');
const resText = todayLoot ? `đŠ ${formatLoot(todayLoot.value)} (Rang ${todayLoot.rank})` : 'đŠ â';
const vilText = todayVillages ? `đïž ${todayVillages.value.toLocaleString('de-DE')} Dörfer (Rang ${todayVillages.rank})` : 'đïž â';
const avg = (todayLoot && todayVillages && todayVillages.value > 0) ? Math.round(todayLoot.value / todayVillages.value) : null;
const avgText = avg ? `⥠${formatLoot(avg)} / Angriff` : '⥠â';
if (mini) mini.innerHTML = `${resText}<br>${vilText}<br>${avgText}`;
if (fullRes) fullRes.innerText = todayLoot ? `${todayLoot.value.toLocaleString('de-DE')} (Rang ${todayLoot.rank})` : 'â';
if (fullVil) fullVil.innerText = todayVillages ? `${todayVillages.value.toLocaleString('de-DE')} (Rang ${todayVillages.rank})` : 'â';
if (fullAvg) fullAvg.innerText = avg ? `${avg.toLocaleString('de-DE')} / Angriff` : 'â';
}
// =====================================================================
// GRUPPE ZURĂCKSETZEN
// =====================================================================
function resetGroupToAll() {
if (settings.optionGroup === 0) return;
// Gruppe im Farm-Assistenten zurĂŒck auf "Alle" (group_id 0)
const url = TribalWars.buildURL('GET', 'am_farm', { ajax: 'change_group', group: 0 });
}
// =====================================================================
// NACHTMODUS & ZUFĂLLIGE PAUSEN
// =====================================================================
let randomPauseTimeout = null;
let isRandomPausing = false;
function isNightMode() {
settings = loadSettings();
if (!settings.nightModeEnabled) return false;
const now = new Date();
const currentMinutes = now.getHours() * 60 + now.getMinutes();
const [startH, startM] = settings.nightStart.split(':').map(Number);
const [endH, endM] = settings.nightEnd.split(':').map(Number);
const startMinutes = startH * 60 + startM;
const endMinutes = endH * 60 + endM;
// Ăber Mitternacht
if (startMinutes > endMinutes) {
return currentMinutes >= startMinutes || currentMinutes < endMinutes;
}
return currentMinutes >= startMinutes && currentMinutes < endMinutes;
}
function getReloadDelay() {
settings = loadSettings();
if (isNightMode()) {
return randomDelay(settings.nightReloadMin, settings.nightReloadMax);
}
return randomDelay(settings.reloadMin, settings.reloadMax);
}
const RANDOM_PAUSE_KEY = domain + '_autofarm_next_pause';
const RANDOM_PAUSE_ACTIVE_KEY = domain + '_autofarm_pause_active';
// Gibt die Reload-Zeit zurĂŒck â bei fĂ€lliger Pause: lĂ€ngeres Intervall
function getRandomPauseReloadDelay(normalDelay) {
settings = loadSettings();
if (!settings.randomPauseEnabled) return normalDelay;
// ZufÀllige Pausen nur im Nachtmodus wenn nightModeEnabled
if (settings.nightModeEnabled && !isNightMode()) return normalDelay;
const now = Date.now();
const nextPause = parseInt(localStorage.getItem(RANDOM_PAUSE_KEY) || '0');
if (nextPause === 0) {
// Ersten Pause-Zeitpunkt setzen
const intervalSec = randomDelay(
settings.randomPauseIntervalMin * 60,
settings.randomPauseIntervalMax * 60
);
localStorage.setItem(RANDOM_PAUSE_KEY, now + intervalSec * 1000);
console.log(`[Autofarm] NĂ€chste Pause in ${Math.round(intervalSec/60)} Min`);
return normalDelay;
}
if (now >= nextPause) {
// Pause fĂ€llig â lĂ€ngeres Reload-Intervall verwenden
const durationSec = randomDelay(
settings.randomPauseDurationMin * 60,
settings.randomPauseDurationMax * 60
);
// NĂ€chste Pause einplanen ab jetzt + Pausendauer + normales Intervall
const intervalSec = randomDelay(
settings.randomPauseIntervalMin * 60,
settings.randomPauseIntervalMax * 60
);
localStorage.setItem(RANDOM_PAUSE_KEY, now + durationSec * 1000 + intervalSec * 1000);
localStorage.setItem(RANDOM_PAUSE_ACTIVE_KEY, '1');
console.log(`[Autofarm] Pause fÀllig: ${Math.round(durationSec/60)} Min lÀngeres Reload`);
isRandomPausing = true;
return durationSec; // Reload-Intervall = Pausendauer
}
return normalDelay;
}
function scheduleRandomPause(callback) {
callback(); // direkt aufrufen â Pause-Logik ist in startCountdown integriert
}
function clearRandomPause() {
if (randomPauseTimeout) { clearTimeout(randomPauseTimeout); randomPauseTimeout = null; }
localStorage.removeItem(RANDOM_PAUSE_KEY);
localStorage.removeItem(RANDOM_PAUSE_ACTIVE_KEY);
isRandomPausing = false;
}
// =====================================================================
// FARMGOD LIBRARY
// =====================================================================
const FarmLib = (function () {
if (typeof window.twLib === 'undefined') {
window.twLib = {
queues: null,
init: function () { if (this.queues === null) this.queues = this.queueLib.createQueues(5); },
queueLib: {
maxAttempts: 3,
Item: function (action, arg, promise = null) { this.action = action; this.arguments = arg; this.promise = promise; this.attempts = 0; },
Queue: function () {
this.list = []; this.working = false; this.length = 0;
this.doNext = function () {
let item = this.dequeue(), self = this;
$[item.action](...item.arguments)
.done(function () { item.promise.resolve.apply(null, arguments); self.start(); })
.fail(function () { item.attempts += 1; if (item.attempts < twLib.queueLib.maxAttempts) self.enqueue(item, true); else item.promise.reject.apply(null, arguments); self.start(); });
};
this.start = function () { if (this.length) { this.working = true; this.doNext(); } else this.working = false; };
this.dequeue = function () { this.length -= 1; return this.list.shift(); };
this.enqueue = function (item, front = false) { front ? this.list.unshift(item) : this.list.push(item); this.length += 1; if (!this.working) this.start(); };
},
createQueues: function (amount) { let arr = []; for (let i = 0; i < amount; i++) arr[i] = new twLib.queueLib.Queue(); return arr; },
addItem: function (item) { let min = twLib.queues.map(q => q.length).reduce((n, c) => (c < n ? c : n), 0); twLib.queues[min].enqueue(item); },
orchestrator: function (type, arg) { let p = $.Deferred(); twLib.queueLib.addItem(new twLib.queueLib.Item(type, arg, p)); return p; },
},
ajax: function () { return twLib.queueLib.orchestrator('ajax', arguments); },
get: function () { return twLib.queueLib.orchestrator('get', arguments); },
post: function () { return twLib.queueLib.orchestrator('post', arguments); },
};
twLib.init();
}
const setUnitSpeeds = () => $.when($.get('/interface.php?func=get_unit_info')).then((xml) => {
let u = {}; $(xml).find('config').children().map((i, el) => { u[$(el).prop('nodeName')] = $(el).find('speed').text().toNumber(); });
localStorage.setItem('FarmGod_unitSpeeds', JSON.stringify(u));
});
const getUnitSpeeds = () => JSON.parse(localStorage.getItem('FarmGod_unitSpeeds')) || false;
if (!getUnitSpeeds()) setUnitSpeeds();
const determineNextPage = (page, $html) => {
let vl = $html.find('#scavenge_mass_screen').length > 0 ? $html.find('tr[id*="scavenge_village"]').length : $html.find('tr.row_a, tr.row_ax, tr.row_b, tr.row_bx').length;
let ns = $html.find('.paged-nav-item').first().closest('td').find('select').first();
let nl = $html.find('#am_widget_Farm').length > 0
? parseInt($('#plunder_list_nav').first().find('a.paged-nav-item, strong.paged-nav-item')[$('#plunder_list_nav').first().find('a.paged-nav-item, strong.paged-nav-item').length - 1].textContent.replace(/\D/g, '')) - 1
: ns.length > 0 ? ns.find('option').length - 1 : $html.find('.paged-nav-item').not('[href*="page=-1"]').length;
let ps = $('#mobileHeader').length > 0 ? 10 : parseInt($html.find('input[name="page_size"]').val());
if (page == -1 && vl == 1000) return Math.floor(1000 / ps);
else if (page < nl) return page + 1;
return false;
};
const processPage = (url, page, wrapFn) => {
const fullUrl = url + (url.match('am_farm') ? `&Farm_page=${page}` : `&page=${page}`);
return twLib.ajax({ url: fullUrl }).then((html) => {
return wrapFn(page, $(html));
}).catch(e => {
console.error(`[AF] processPage ERROR p=${page}:`, e);
throw e;
});
};
const processAllPages = (url, processorFn) => {
let page = url.match('am_farm') || url.match('scavenge_mass') ? 0 : -1;
let wrapFn = (page, $html) => { let dnp = determineNextPage(page, $html);if (dnp) { processorFn($html); return processPage(url, dnp, wrapFn); } else return processorFn($html); };
return processPage(url, page, wrapFn);
};
const getDistance = (o, t) => Math.hypot(o.toCoord(true).x - t.toCoord(true).x, o.toCoord(true).y - t.toCoord(true).y);
const subtractArrays = (a1, a2) => { let r = a1.map((v, i) => v - a2[i]); return r.some(v => v < 0) ? false : r; };
const getCurrentServerTime = () => { let [h, m, s, d, mo, y] = $('#serverTime').closest('p').text().match(/\d+/g); return new Date(y, mo - 1, d, h, m, s).getTime(); };
const timestampFromString = (timestr) => {
try {
const lang = (typeof unsafeWindow !== 'undefined' ? unsafeWindow : window).lang;
if (!lang) return 0;
let d = $('#serverDate').text().split('/').map(x => +x);
let tp = new RegExp(lang['aea2b0aa9ae1534226518faaefffdaad'].replace('%s', '([\\d+|:]+)')).exec(timestr);
let tom = new RegExp(lang['57d28d1b211fddbb7a499ead5bf23079'].replace('%s', '([\\d+|:]+)')).exec(timestr);
let lat = new RegExp(lang['0cb274c906d622fa8ce524bcfbb7552d'].replace('%1', '([\\d+|\\.]+)').replace('%2', '([\\d+|:]+)')).exec(timestr);
let t, date;
if (tp) { t = tp[1].split(':'); date = new Date(d[2], d[1]-1, d[0], t[0], t[1], t[2], t[3]||0); }
else if (tom) { t = tom[1].split(':'); date = new Date(d[2], d[1]-1, d[0]+1, t[0], t[1], t[2], t[3]||0); }
else if (lat) { d = (lat[1]+d[2]).split('.').map(x=>+x); t = lat[2].split(':'); date = new Date(d[2], d[1]-1, d[0], t[0], t[1], t[2], t[3]||0); }
else return 0;
return date.getTime();
} catch(e) { return 0; }
};
if (!String.prototype.toCoord) String.prototype.toCoord = function (o) { let c = (this.match(/\d{1,3}\|\d{1,3}/g)||[false]).pop(); return c && o ? {x:c.split('|')[0],y:c.split('|')[1]} : c; };
if (!String.prototype.toNumber) String.prototype.toNumber = function () { return parseFloat(this); };
if (!Number.prototype.toNumber) Number.prototype.toNumber = function () { return parseFloat(this); };
return { getUnitSpeeds, processPage, processAllPages, getDistance, subtractArrays, getCurrentServerTime, timestampFromString };
})();
// =====================================================================
// KATTA ENGINE â Rot/Gelb Angriffe
// =====================================================================
const KATTA_SENT_KEY = domain + '_katta_sent';
let kattaAllFarms = {}; // wird nach getData befĂŒllt // {coord: timestamp}
function kattaLoadSent() {
try { return JSON.parse(localStorage.getItem(KATTA_SENT_KEY) || '{}'); } catch(e) { return {}; }
}
function kattaSaveSent(obj) { localStorage.setItem(KATTA_SENT_KEY, JSON.stringify(obj)); }
function kattaShouldAttack(coord, color) {
const s = loadSettings();
if (!s.kattaEnabled) return false;
if (color === 'red' && !s.kattaOnRed) return false;
if (color === 'yellow' && !s.kattaOnYellow) return false;
if (color !== 'red' && color !== 'yellow') return false;
const sent = kattaLoadSent();
if (!sent[coord]) return true; // noch nie geschickt
const age = Date.now() - sent[coord];
return age >= 24 * 60 * 60 * 1000; // Àlter als 24h
}
function kattaMarkSent(coord) {
const sent = kattaLoadSent();
sent[coord] = Date.now();
kattaSaveSent(sent);
}
// WÀhlt nÀchstes Dorf aus Gruppe das genug Truppen hat
// LĂ€dt unitMap mit direktem fetch (nicht twLib-Queue) um Farm-Loop nicht zu blockieren
async function kattaLoadUnitMap(group) {
const gd = (typeof unsafeWindow !== 'undefined' ? unsafeWindow : window).game_data;
const csrf = gd && gd.csrf;
const villages = await FarmGodCore.loadVillages(group);
const unitMap = {};
const fetchPage = async (page) => {
const url = `/game.php?village=${gd.village.id}&screen=overview_villages&mode=combined&group=${group}&page=${page}&h=${csrf}`;
const html = await fetch(url, { credentials: 'include' }).then(r => r.text());
const $html = $(html);
const units = gd.units; // ["spear","sword","axe",...]
$html.find('#combined_table').find('.row_a,.row_b').each((i, el) => {
const $el = $(el);
const coord = $el.find('.quickedit-label').first().text().trim().match(/\d+\|\d+/)?.[0];
if (!coord) return;
const unitItems = $el.find('.unit-item');
unitMap[coord] = {
id: villages[coord] && villages[coord].id,
axe: parseInt(unitItems.eq(units.indexOf('axe')).text()) || 0,
ram: parseInt(unitItems.eq(units.indexOf('ram')).text()) || 0,
spy: parseInt(unitItems.eq(units.indexOf('spy')).text()) || 0,
catapult: parseInt(unitItems.eq(units.indexOf('catapult')).text()) || 0,
};
});
// NĂ€chste Seite prĂŒfen
const nextMatch = $html.find('#pager_form').find('input[name=page]').attr('max');
return nextMatch ? parseInt(nextMatch) : page;
};
let page = 0;
const maxPage = await fetchPage(0);
for (let p = 1; p <= maxPage; p++) {
await fetchPage(p);
}
return unitMap;
}
// WÀhlt nÀchstes Dorf aus unitMap das genug Truppen hat (nach Distanz)
function kattaFindVillage(targetCoord, troops, unitMap) {
const candidates = Object.entries(unitMap)
.filter(([, u]) =>
u.id &&
u.axe >= troops.axe &&
u.ram >= troops.ram &&
u.spy >= troops.spy &&
u.catapult >= troops.catapult
)
.map(([coord, u]) => ({ coord, id: u.id, dist: FarmLib.getDistance(coord, targetCoord) }))
.sort((a, b) => a.dist - b.dist);
return candidates[0] || null;
}
// Zieht verplante Truppen lokal ab
function kattaReserveTroops(unitMap, coord, troops) {
if (!unitMap[coord]) return;
unitMap[coord].axe -= troops.axe;
unitMap[coord].ram -= troops.ram;
unitMap[coord].spy -= troops.spy;
unitMap[coord].catapult -= troops.catapult;
}
async function kattaSendAttack(villageId, target, troops, building) {
const base = window.location.origin + '/game.php';
const gd = (typeof unsafeWindow !== 'undefined' ? unsafeWindow : window).game_data;
const csrf = gd && gd.csrf;
if (!csrf) throw new Error('CSRF fehlt');
// Schritt 1: Versammlungsplatz im iframe laden â CSRF-Feldname holen
const { tokenField, tokenValue } = await new Promise((resolve, reject) => {
const iframe = document.createElement('iframe');
iframe.style.cssText = 'display:none;width:0;height:0;border:0;';
iframe.src = `${base}?village=${villageId}&screen=place`;
iframe.onload = () => {
try {
const iDoc = iframe.contentDocument || iframe.contentWindow.document;
let tokenField = null, tokenValue = null;
iDoc.querySelectorAll('input[type=hidden]').forEach(inp => {
if (/^[0-9a-f]{10,}$/.test(inp.name)) { tokenField = inp.name; tokenValue = inp.value; }
});
document.body.removeChild(iframe);
if (tokenField) resolve({ tokenField, tokenValue });
else reject(new Error('Token nicht im iframe'));
} catch(e) { document.body.removeChild(iframe); reject(e); }
};
iframe.onerror = () => { document.body.removeChild(iframe); reject(new Error('iframe Fehler')); };
document.body.appendChild(iframe);
});
// Schritt 2: try=confirm
const step2params = new URLSearchParams({
source_village: villageId,
x: target.x,
y: target.y,
target_type: 'coord',
input: `${target.x}|${target.y}`,
attack: 'Angreifen',
template_id: '',
spy: troops.spy || 0,
axe: troops.axe || 0,
ram: troops.ram || 0,
catapult: troops.catapult || 0,
spear: 0, sword: 0, archer: 0, light: 0, marcher: 0, heavy: 0, snob: 0,
});
step2params.set(tokenField, tokenValue);
const step2html = await fetch(`${base}?village=${villageId}&screen=place&try=confirm&h=${csrf}`, {
method: 'POST', credentials: 'include',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: step2params.toString(),
}).then(r => r.text());
// Schritt 3: BestÀtigung
const step3params = new URLSearchParams();
for (const [, name, value] of step2html.matchAll(/name="([^"]+)"\s+value="([^"]*)"/g)) {
step3params.set(name, value);
}
if (!step3params.has('ch')) throw new Error('ch-Token fehlt in BestÀtigungsseite');
// Katapult-ZielgebÀude setzen falls in BestÀtigungsseite ein building-Select vorhanden
step3params.set('building', building || 'wall');
step3params.set('h', csrf);
step3params.set('cb', 'troop_confirm_submit');
step3params.set('submit_confirm', 'Angreifen');
step3params.set('attack_name', '');
const step3res = await fetch(`${base}?village=${villageId}&screen=place&action=command`, {
method: 'POST', credentials: 'include',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Referer': `${base}?village=${villageId}&screen=place&try=confirm`,
},
body: step3params.toString(),
});
if (!step3res.ok) throw new Error(`HTTP ${step3res.status}`);
}
// Hauptfunktion: alle rot/gelben Farmen prĂŒfen und Katta-Angriffe schicken
let kattaPhaseRunning = false;
async function runKattaPhase(farms) {
const s = loadSettings();
if (!s.kattaEnabled) return;
if (kattaPhaseRunning) { console.log('[Katta] Phase lĂ€uft bereits â ĂŒbersprungen'); return; }
kattaPhaseRunning = true;
const troops = {
axe: parseInt(s.kattaAxe) || 0,
ram: parseInt(s.kattaRam) || 0,
spy: parseInt(s.kattaSpy) || 0,
catapult: parseInt(s.kattaCata) || 0,
};
// Katta-Log bereinigen: Dörfer die jetzt grĂŒn/blau sind rausnehmen
const sent = kattaLoadSent();
let cleaned = 0;
Object.entries(farms).forEach(([coord, f]) => {
if (sent[coord] && (f.color === 'green' || f.color === 'blue')) {
delete sent[coord];
cleaned++;
}
});
if (cleaned > 0) { kattaSaveSent(sent); console.log(`[Katta] ${cleaned} Dörfer aus Log entfernt (jetzt grĂŒn/blau)`); }
// Duplikate vermeiden: Set der bereits in dieser Runde angegriffenen Coords
const attackedThisRound = new Set();
const targets = Object.entries(farms)
.filter(([coord, f]) => kattaShouldAttack(coord, f.color))
.filter(([coord]) => { if (attackedThisRound.has(coord)) return false; attackedThisRound.add(coord); return true; })
.map(([coord, f]) => ({ coord, f }));
if (!targets.length) return;
setStatus(`âïž ${targets.length} Katta-Angriffe werden geprĂŒft...`, '#e67e22');
// Einmalig unitMap laden â Truppen werden lokal abgezogen
const unitMap = await kattaLoadUnitMap(s.kattaGroup);
let sentCount = 0;
for (const { coord } of targets) {
try {
const [x, y] = coord.split('|');
const village = kattaFindVillage(coord, troops, unitMap);
if (!village) {
console.log(`[Katta] Kein Dorf mit Truppen fĂŒr ${coord} â ĂŒbersprungen`);
continue;
}
await kattaSendAttack(village.id, { x, y }, troops, s.kattaTarget);
kattaReserveTroops(unitMap, village.coord, troops); // lokal abziehen
kattaMarkSent(coord);
sentCount++;
console.log(`[Katta] â ${coord} von ${village.coord} (${sentCount}/${targets.length})`);
if (targets.indexOf(targets.find(t => t.coord === coord)) < targets.length - 1) {
await new Promise(r => setTimeout(r, 500 + Math.random() * 200));
}
} catch(e) {
console.error(`[Katta] Fehler bei ${coord}:`, e);
}
}
setStatus(`â
${sentCount} Katta-Angriffe gesendet`, '#27ae60');
kattaPhaseRunning = false;
}
// =====================================================================
// FARMGOD KERN
// =====================================================================
const FarmGodCore = (function (lib) {
let farmBusy = false;
const buildGroupSelect = (id) => $.get(TribalWars.buildURL('GET', 'groups', { ajax: 'load_group_menu' })).then((groups) => {
let html = `<select class="optionGroup" style="background:#1a1a1a;color:#fff;border:1px solid #555;border-radius:4px;padding:2px;width:100%;">`;
groups.result.forEach(val => { if (val.type == 'separator') html += `<option disabled=""/>`; else html += `<option value="${val.group_id}" ${val.group_id == id ? 'selected' : ''}>${val.name}</option>`; });
return html + `</select>`;
});
const loadVillages = (group) => {
let villages = {};
let processor = ($html) => {
const mobile = $('#mobileHeader').length > 0;
if (mobile) {
jQuery($html).find('.overview-container > div').each((i, el) => {
try { const id=jQuery(el).find('.quickedit-vn').data('id'),name=jQuery(el).find('.quickedit-label').attr('data-text'),coord=jQuery(el).find('.quickedit-label').text().toCoord(); if(coord)villages[coord]={name,id}; } catch(e){}
});
} else {
$html.find('#combined_table').find('.row_a,.row_b').filter((i,el)=>$(el).find('.bonus_icon_33').length==0).map((i,el)=>{
let $qel=$(el).find('.quickedit-label').first(),coord=$qel.text().toCoord();
if(coord)villages[coord]={name:$qel.data('text'),id:parseInt($(el).find('.quickedit-vn').first().data('id'))};
});
}
return villages;
};
return lib.processAllPages(TribalWars.buildURL('GET','overview_villages',{mode:'combined',group,page_size:1000}),processor).then(()=>villages);
};
const getData = (group, newbarbs, losses) => {
const game_data = (typeof unsafeWindow !== 'undefined' ? unsafeWindow : window).game_data;
let data = { villages:{}, commands:{}, farms:{templates:{},farms:{}} };
let skipUnits = ['ram','catapult','knight','snob','militia'];
let villagesProcessor = ($html) => {
const mobile = $('#mobileHeader').length > 0;
if (mobile) {
jQuery($html).find('.overview-container > div').each((i,el)=>{
try {
const vid=jQuery(el).find('.quickedit-vn').data('id'),name=jQuery(el).find('.quickedit-label').attr('data-text'),coord=jQuery(el).find('.quickedit-label').text().toCoord();
const units=new Array(game_data.units.length).fill(0);
jQuery(el).find('.overview-units-row > div.unit-row-item').each((_,ue)=>{const img=jQuery(ue).find('img'),span=jQuery(ue).find('span.unit-row-name');if(img.length&&span.length){let ut=img.attr('src').split('unit_')[1].replace('@2x.webp','').replace('.webp','').replace('.png','');const ui=game_data.units.indexOf(ut);if(ui!==-1)units[ui]=parseInt(span.text())||0;}});
data.villages[coord]={name,id:vid,units:units.filter((_,i)=>skipUnits.indexOf(game_data.units[i])===-1)};
} catch(e){}
});
} else {
$html.find('#combined_table').find('.row_a,.row_b').filter((i,el)=>$(el).find('.bonus_icon_33').length==0).map((i,el)=>{
let $el=$(el),$qel=$el.find('.quickedit-label').first();
let units=$el.find('.unit-item').filter((idx)=>skipUnits.indexOf(game_data.units[idx])==-1).map((idx,e)=>$(e).text().toNumber()).get();
return(data.villages[$qel.text().toCoord()]={name:$qel.data('text'),id:parseInt($el.find('.quickedit-vn').first().data('id')),units});
});
}
return data;
};
let commandsProcessor = ($html) => {
$html.find('#commands_table').find('.row_a,.row_ax,.row_b,.row_bx').map((i,el)=>{
let $el=$(el),coord=$el.find('.quickedit-label').first().text().toCoord();
if(coord){if(!data.commands[coord])data.commands[coord]=[];data.commands[coord].push(Math.round(lib.timestampFromString($el.find('td').eq(2).text().trim())/1000));}
});
return data;
};
let farmProcessor = ($html) => {
if($.isEmptyObject(data.farms.templates)){
let us=lib.getUnitSpeeds();
$html.find('form[action*="action=edit_all"]').find('input[type="hidden"][name*="template"]').closest('tr').map((i,el)=>{
let $el=$(el);
return(data.farms.templates[$el.prev('tr').find('a.farm_icon').first().attr('class').match(/farm_icon_(.*)\s/)[1]]={
id:$el.find('input[type="hidden"][name*="template"][name*="[id]"]').first().val().toNumber(),
units:$el.find('input[type="text"],input[type="number"]').map((i,e)=>$(e).val().toNumber()).get(),
speed:Math.max(...$el.find('input[type="text"],input[type="number"]').map((i,e)=>$(e).val().toNumber()>0?us[$(e).attr('name').trim().split('[')[0]]:0).get()),
});
});
}
$html.find('#plunder_list').find('tr[id^="village_"]').map((i,el)=>{
let $el=$(el);
return(data.farms.farms[$el.find('a[href*="screen=report&mode=all&view="]').first().text().toCoord()]={
id:$el.attr('id').split('_')[1].toNumber(),
color:$el.find('img[src*="graphic/dots/"]').attr('src').match(/dots\/(green|yellow|red|blue|red_blue)/)[1],
max_loot:$el.find('img[src*="max_loot/1"]').length>0,
});
});
return data;
};
let findNewbarbs = () => newbarbs ? twLib.get('/map/village.txt').then((all)=>{
all.match(/[^\r\n]+/g).forEach(vd=>{let [id,name,x,y,pid]=vd.split(','),coord=`${x}|${y}`;if(pid==0&&!data.farms.farms[coord])data.farms.farms[coord]={id:id.toNumber()};});
return data;
}) : data;
let filterFarms = () => {
data.farms.allFarms = Object.assign({}, data.farms.farms); // alle Farmen vor Filter merken
data.farms.farms=Object.fromEntries(Object.entries(data.farms.farms).filter(([k,v])=>!v.color||(v.color!='red'&&v.color!='red_blue'&&(v.color!='yellow'||losses))));
return data;
};
return Promise.all([
lib.processAllPages(TribalWars.buildURL('GET','overview_villages',{mode:'combined',group}),villagesProcessor).catch(e=>console.warn('[AF] villages error:',e)),
lib.processAllPages(TribalWars.buildURL('GET','overview_villages',{mode:'commands',type:'attack'}),commandsProcessor).catch(e=>console.warn('[AF] commands error:',e)),
lib.processAllPages(TribalWars.buildURL('GET','am_farm'),farmProcessor).catch(e=>console.warn('[AF] farm error:',e)),
Promise.resolve(findNewbarbs()).catch(e=>console.warn('[AF] newbarbs error:',e)),
]).then(filterFarms).then(()=>data);
};
const isInArea = (farmCoord, x1, y1, x2, y2) => {
const fc = farmCoord.toCoord(true);
if (!fc) return false;
const fx = parseFloat(fc.x), fy = parseFloat(fc.y);
const minX = Math.min(x1, x2), maxX = Math.max(x1, x2);
const minY = Math.min(y1, y2), maxY = Math.max(y1, y2);
return fx >= minX && fx <= maxX && fy >= minY && fy <= maxY;
};
const farmAllowed = (farmCoord, dist, vs, optionDistance) => {
if (!vs) return dist < optionDistance; // kein individuelles Setting â Standard
const mode = vs.mode || 'distance';
const maxDist = vs.distance !== undefined ? parseFloat(vs.distance) : optionDistance;
const areaOk = isInArea(farmCoord,
vs.x1 !== undefined ? vs.x1 : 0,
vs.y1 !== undefined ? vs.y1 : 0,
vs.x2 !== undefined ? vs.x2 : 999,
vs.y2 !== undefined ? vs.y2 : 999);
if (mode === 'distance') return dist < maxDist;
if (mode === 'area') return areaOk;
if (mode === 'both') return dist < maxDist && areaOk;
return dist < optionDistance;
};
const createPlanning = (optionDistance, optionTime, optionMaxloot, data, villageDistances, villageSettings) => {
let plan={counter:0,farms:{}};
let serverTime=Math.round(lib.getCurrentServerTime()/1000);
for(let prop in data.villages){
const vs = villageSettings && villageSettings[prop] ? villageSettings[prop] : null;
// Fallback auf altes villageDistances System
let distForVillage;
if (!vs && villageDistances && villageDistances[prop] !== undefined) {
distForVillage = parseFloat(villageDistances[prop]);
} else {
distForVillage = optionDistance;
}
let orderedFarms=Object.keys(data.farms.farms).map(k=>({coord:k,dis:lib.getDistance(prop,k)})).sort((a,b)=>a.dis>b.dis?1:-1);
orderedFarms.forEach(el=>{
let fi=data.farms.farms[el.coord],tn=optionMaxloot&&fi.max_loot?'b':'a',tmpl=data.farms.templates[tn];
let unitsLeft=lib.subtractArrays(data.villages[prop].units,tmpl.units);
let dist=lib.getDistance(prop,el.coord),arrival=Math.round(serverTime+dist*tmpl.speed*60+Math.round(plan.counter/5));
let maxTD=Math.round(optionTime*60),timeDiff=true;
if(data.commands[el.coord]){if(!fi.color&&data.commands[el.coord].length>0)timeDiff=false;data.commands[el.coord].forEach(ts=>{if(Math.abs(ts-arrival)<maxTD){timeDiff=false;}});}
else data.commands[el.coord]=[];
const allowed = vs ? farmAllowed(el.coord, dist, vs, optionDistance) : dist < distForVillage;
if(unitsLeft&&timeDiff&&allowed){
plan.counter++;
if(!plan.farms[prop])plan.farms[prop]=[];
plan.farms[prop].push({origin:{coord:prop,name:data.villages[prop].name,id:data.villages[prop].id},target:{coord:el.coord,id:fi.id},fields:dist,template:{name:tn,id:tmpl.id}});
data.villages[prop].units=unitsLeft;data.commands[el.coord].push(arrival);
}
});
}
return plan;
};
const buildTable = (plan) => {
let html=`<div class="vis farmGodContent"><h4>FarmGod</h4><table class="vis" width="100%">
<tr><div id="FarmGodProgessbar" class="progress-bar live-progress-bar progress-bar-alive" style="width:98%;margin:5px auto;"><div style="background:rgb(146,194,0);"></div><span class="label" style="margin-top:0px;"></span></div></tr>
<tr><th style="text-align:center;">Herkunft</th><th style="text-align:center;">Ziel</th><th style="text-align:center;">Felder</th><th style="text-align:center;">Farm</th></tr>`;
if(!$.isEmptyObject(plan)){for(let prop in plan)plan[prop].forEach((val,i)=>{html+=`<tr class="farmRow row_${i%2==0?'a':'b'}"><td style="text-align:center;"><a href="${game_data.link_base_pure}info_village&id=${val.origin.id}">${val.origin.name} (${val.origin.coord})</a></td><td style="text-align:center;"><a href="${game_data.link_base_pure}info_village&id=${val.target.id}">${val.target.coord}</a></td><td style="text-align:center;">${val.fields.toFixed(2)}</td><td style="text-align:center;"><a href="#" data-origin="${val.origin.id}" data-target="${val.target.id}" data-template="${val.template.id}" class="farmGod_icon farm_icon farm_icon_${val.template.name}" style="margin:auto;"></a></td></tr>`;});}
else html+=`<tr><td colspan="4" style="text-align:center;">Keine Farms mit den aktuellen Einstellungen möglich.</td></tr>`;
return html+`</table></div>`;
};
const sendFarm = ($this) => {
let n=Timing.getElapsedTimeSinceLoad();
if(!farmBusy&&!(Accountmanager.farm.last_click&&n-Accountmanager.farm.last_click<200)){
farmBusy=true;Accountmanager.farm.last_click=n;let $pb=$('#FarmGodProgessbar');
TribalWars.post(Accountmanager.send_units_link.replace(/village=(\d+)/,'village='+$this.data('origin')),null,
{target:$this.data('target'),template_id:$this.data('template'),source:$this.data('origin')},
(r)=>{UI.SuccessMessage(r.success);$pb.data('current',$pb.data('current')+1);UI.updateProgressBar($pb,$pb.data('current'),$pb.data('max'));$this.closest('.farmRow').remove();farmBusy=false;},
(r)=>{UI.ErrorMessage(r||'Fehler!');$pb.data('current',$pb.data('current')+1);UI.updateProgressBar($pb,$pb.data('current'),$pb.data('max'));$this.closest('.farmRow').remove();farmBusy=false;}
);
}
};
const bindEventHandlers = () => {
$('.farmGod_icon').off('click').on('click',function(){sendFarm($(this));});
$(document).off('keydown.farmgod').on('keydown.farmgod',(e)=>{if((e.keyCode||e.which)==13)$('.farmGod_icon').first().trigger('click');});
};
const planAndRender = () => {
settings=loadSettings();
const gd2 = (typeof unsafeWindow !== 'undefined' ? unsafeWindow : window).game_data;
if(!gd2||!gd2.features.Premium.active||!gd2.features.FarmAssistent.active){UI.ErrorMessage('Farmgod benötigt Premium + Farm-Assistent!');return Promise.reject();}
setStatus('âł Lade Daten...','#f0a500');
const effectiveTime = isNightMode() ? settings.nightOptionTime : settings.optionTime;
const effectiveMaxloot = isNightMode() ? settings.nightOptionMaxloot : settings.optionMaxloot;
const effectiveLosses = isNightMode() ? settings.nightOptionLosses : settings.optionLosses;
return getData(settings.optionGroup,settings.optionNewbarbs,effectiveLosses).then(data=>{
let plan=createPlanning(settings.optionDistance,effectiveTime,effectiveMaxloot,data,settings.villageDistances,settings.villageSettings);
$('.farmGodContent').remove();$('#am_widget_Farm').first().before(buildTable(plan.farms));
bindEventHandlers();UI.InitProgressBars();UI.updateProgressBar($('#FarmGodProgessbar'),0,plan.counter);$('#FarmGodProgessbar').data('current',0).data('max',plan.counter);
setStatus(`â
${plan.counter} Farms geplant`,'#27ae60');
kattaAllFarms = data.farms.allFarms || {}; // fĂŒr Katta-Phase merken
return plan;
});
};
return { planAndRender, buildGroupSelect, loadVillages };
})(FarmLib);
// =====================================================================
// ENTER / STATUS
// =====================================================================
function fireEnterDown(r){const o={key:'Enter',code:'Enter',keyCode:13,which:13,bubbles:true,cancelable:true,repeat:r};document.dispatchEvent(new KeyboardEvent('keydown',o));document.dispatchEvent(new KeyboardEvent('keypress',o));}
function fireEnterUp(){document.dispatchEvent(new KeyboardEvent('keyup',{key:'Enter',code:'Enter',keyCode:13,which:13,bubbles:true,cancelable:true,repeat:false}));}
function setStatus(text,color){
currentStatus=text;
const el=document.getElementById('af-status-mini');if(el){el.innerText=text;el.style.color=color||'#f0a500';}
const full=document.getElementById('af-countdown');if(full){full.innerText=text;full.style.color=color||'#f0a500';full.style.display='block';}
// Tag/Nacht Modus anzeigen
const modeEl=document.getElementById('af-mode-mini');
if(modeEl){
if(isNightMode()){modeEl.innerText='đ Nachtmodus';modeEl.style.color='#8e44ad';}
else{modeEl.innerText='âïž Tagmodus';modeEl.style.color='#f0a500';}
}
}
function setCountdown(text){
currentCountdownText=text;
const el=document.getElementById('af-timer-mini');if(el)el.innerText=text;
const full=document.getElementById('af-countdown');if(full){full.innerText=text;full.style.display='block';}
}
function getFarmGodProgress(){
const pb=document.getElementById('FarmGodProgessbar');
if(!pb)return null;
const label=pb.querySelector('.label');
if(!label)return null;
const text=(label.innerText||label.textContent||'').trim().replace(/\./g,'');
const m=text.match(/^(\d+)\s*\/\s*(\d+)$/);
if(m)return{current:parseInt(m[1]),total:parseInt(m[2])};
return null;
}
function hasFarmsRemaining(){
const p=getFarmGodProgress();
if(p!==null){
if(p.total===0)return false;
return p.current<p.total;
}
const visibleRows=document.querySelectorAll('.farmRow');
return visibleRows.length>0;
}
function startHoldEnter(){
holdRepeatCount=0;fireEnterDown(false);holdRepeatCount++;
holdIntervalId=setInterval(()=>{
if(!isRunning){stopHoldEnter();return;}
fireEnterDown(true);holdRepeatCount++;
const p=getFarmGodProgress();
if(p){
if(p.total===0){stopHoldEnter();setStatus('đŽ Keine Farms','#aaa');triggerEndOfLoop();return;}
if(p.current>=p.total){stopHoldEnter();setStatus('â
Alle erledigt','#27ae60');triggerEndOfLoop();return;}
setStatus(`âïž Farmt... ${p.current}/${p.total}`,'#27ae60');
}
},settings.holdInterval);
}
function stopHoldEnter(){clearInterval(holdIntervalId);fireEnterUp();holdRepeatCount=0;}
function pressEnterRandomly(){
settings=loadSettings();const delay=randomDelay(settings.enterMin,settings.enterMax);
fireEnterDown(false);fireEnterUp();
setTimeout(()=>{
if(!hasFarmsRemaining()){emptyFarmChecks++;if(emptyFarmChecks>=EMPTY_FARM_THRESHOLD){clearTimeout(intervalId);setStatus('đ Fertig','#e74c3c');triggerEndOfLoop();return;}}
else{emptyFarmChecks=0;const p=getFarmGodProgress();setStatus(p&&p.total>0?`âïž Farmt... ${p.current}/${p.total}`:'âïž Farmt...','#27ae60');}
},300);
intervalId=setTimeout(pressEnterRandomly,delay);
}
function triggerEndOfLoop(){
resetGroupToAll();
fetchTodayLoot();
scheduleRandomPause(startCountdown);
}
// =====================================================================
// CAPTCHA
// =====================================================================
function isCaptchaPresent(){return['#captcha','.captcha','iframe[src*="captcha"]','iframe[src*="recaptcha"]','.g-recaptcha','#recaptcha','[class*="captcha"]','[id*="captcha"]'].some(s=>document.querySelector(s));}
function handleCaptchaDetected(){
if(captchaAlertSent)return;
captchaAlertSent=true;
localStorage.setItem(domain + '_captcha_paused', 'true');
sendDiscordCaptchaAlert();
// Pausieren statt stoppen
clearTimeout(intervalId);
stopHoldEnter();
clearInterval(countdownInterval);
clearRandomPause();
// isRunning bleibt true â Script ist pausiert nicht gestoppt
setStatus('â ïž Captcha â bitte lösen!','#e74c3c');
// Fortsetzen Button anzeigen
const resumeBtn = document.getElementById('af-resume-btn');
if(resumeBtn) resumeBtn.style.display = 'block';
const miniResumeBtn2 = document.getElementById('af-mini-resume-btn');
if(miniResumeBtn2) miniResumeBtn2.style.display = 'inline-block';
// Automatisch prĂŒfen ob Captcha weg ist nach jedem Reload
// (wird durch startCaptchaObserver + isCaptchaPresent beim nÀchsten Seitenload gehandelt)
console.log('[Autofarm] Captcha erkannt â pausiert. Bitte lösen und Seite neu laden oder Fortsetzen klicken.');
}
function resumeAfterCaptcha(){
if(isCaptchaPresent()){
setStatus('â ïž Captcha noch vorhanden!','#e74c3c');
return;
}
// Alle laufenden Intervalle/Timeouts sauber stoppen
clearTimeout(intervalId);
clearInterval(countdownInterval);
clearInterval(holdIntervalId);
stopHoldEnter();
clearRandomPause();
captchaAlertSent=false;
localStorage.removeItem(domain + '_captcha_paused');
emptyFarmChecks=0;
const resumeBtn=document.getElementById('af-resume-btn');
if(resumeBtn)resumeBtn.style.display='none';
const miniResumeBtn=document.getElementById('af-mini-resume-btn');
if(miniResumeBtn)miniResumeBtn.style.display='none';
setStatus('âł Fortsetze...','#f0a500');
startProcess();
updateButtonState();
}
function startCaptchaObserver(){
if(isCaptchaPresent()){
// Captcha noch da â pausieren
handleCaptchaDetected();
return;
}
if(captchaAlertSent && isRunning){
// Captcha war da aber ist jetzt weg nach Reload
// startProcess lÀuft bereits (wegen isRunning=true aus localStorage)
// nur State zurĂŒcksetzen, kein erneuter startProcess
console.log('[Autofarm] Captcha gelöst â setze State zurĂŒck.');
captchaAlertSent=false;
localStorage.removeItem(domain + '_captcha_paused');
const resumeBtn=document.getElementById('af-resume-btn');
if(resumeBtn)resumeBtn.style.display='none';
const miniResumeBtn=document.getElementById('af-mini-resume-btn');
if(miniResumeBtn)miniResumeBtn.style.display='none';
}
const o=new MutationObserver(()=>{if(isRunning&&isCaptchaPresent())handleCaptchaDetected();});
o.observe(document.body,{childList:true,subtree:true});
}
// =====================================================================
// PROZESS
// =====================================================================
function checkFarmsAndStart(){
const p=getFarmGodProgress();
if(p&&p.total===0){setStatus('đŽ Keine Farms','#aaa');triggerEndOfLoop();return;}
settings=loadSettings();
if(settings.holdEnter){setStatus('âïž Farmt...','#27ae60');startHoldEnter();}
else{setStatus('âïž Farmt...','#27ae60');pressEnterRandomly();}
}
function startProcess(){
captchaAlertSent=false;
localStorage.removeItem(domain + '_captcha_paused');
emptyFarmChecks=0;
startHourlyAlert();
// Reihenfolge: 1) am_farm laden+filtern (in planAndRender via kattaAllFarms)
// 2) Katta-Angriffe planen+senden
// 3) Farm-Angriffe planen+senden
setStatus('âł Lade Farm-Daten...','#f0a500');
FarmGodCore.planAndRender()
.then(() => {
const targets = Object.entries(kattaAllFarms).filter(([coord,f]) => kattaShouldAttack(coord, f.color));
if (targets.length === 0) return;
return runKattaPhase(kattaAllFarms);
})
.catch(e => console.error('[Katta] Fehler:', e))
.then(()=>setTimeout(()=>checkFarmsAndStart(),randomDelay(1000,2000)))
.catch(e=>{console.error(e);setStatus('â Fehler','#e74c3c');});
}
function stopProcess(){
clearTimeout(intervalId);
stopHoldEnter();
clearInterval(countdownInterval);
stopHourlyAlert();
clearRandomPause();
isRunning=false;
localStorage.setItem(domain+'_isRunning',false);
updateButtonState();
if(!captchaAlertSent){
setStatus('âč Gestoppt','#aaa');
const cd=document.getElementById('af-countdown');
if(cd)cd.style.display='none';
}
}
function toggleProcess(){
if(isRunning)stopProcess();
else{
isRunning=true;
localStorage.setItem(domain+'_isRunning',true);
updateButtonState();
startProcess();
}
}
function startCountdown(){
clearInterval(countdownInterval); // sicherstellen kein doppelter Countdown
settings=loadSettings();
const nightMode = isNightMode();
let normalDelay = nightMode
? randomDelay(settings.nightReloadMin, settings.nightReloadMax)
: randomDelay(settings.reloadMin, settings.reloadMax);
let t = getRandomPauseReloadDelay(normalDelay);
const isPause = localStorage.getItem(RANDOM_PAUSE_ACTIVE_KEY) === '1';
if (isPause) localStorage.removeItem(RANDOM_PAUSE_ACTIVE_KEY);
const modeLabel = isPause ? 'đŽ' : (nightMode ? 'đ' : 'đ');
if (isPause) setStatus(`đŽ Pause ${Math.round(t/60)} Min...`, '#8e44ad');
countdownInterval=setInterval(()=>{
if(t<=0){
clearInterval(countdownInterval);
isRandomPausing=false;
location.reload();
} else {
setCountdown(`${modeLabel} ${isPause?'Pause':'Loop'} in: ${Math.floor(t/60)}m ${t%60}s`);
t--;
}
},1000);
}
function updateButtonState(){
const btn=document.getElementById('af-toggle-btn');
const miniBtn=document.getElementById('af-mini-toggle-btn');
const label=isRunning?'Stop Looting':'Start Looting';
const color=isRunning?'#c0392b':'#27ae60';
if(btn){btn.textContent=label;btn.style.backgroundColor=color;}
if(miniBtn){miniBtn.textContent=isRunning?'Stop':'Start';miniBtn.style.backgroundColor=color;}
}
function toggleMinimize(){isMinimized=!isMinimized;saveMinimized(isMinimized);applyMinimizeState();}
function applyMinimizeState(){
const body=document.getElementById('af-body');
const mini=document.getElementById('af-mini-status');
const arrow=document.getElementById('af-arrow');
const miniToggle=document.getElementById('af-mini-toggle-btn');
if(!body||!mini||!arrow)return;
if(isMinimized){
body.style.display='none';
mini.style.display='flex';
arrow.textContent='âŒ';
if(miniToggle)miniToggle.style.display='block';
}else{
body.style.display='block';
mini.style.display='none';
arrow.textContent='âČ';
if(miniToggle)miniToggle.style.display='none';
}
}
function makeDraggable(container,handle){
let d=false,sx,sy,sl,st;
handle.style.cursor='grab';
handle.addEventListener('mousedown',(e)=>{if(['af-arrow','af-toggle-btn'].includes(e.target.id))return;d=true;sx=e.clientX;sy=e.clientY;sl=parseInt(container.style.left)||0;st=parseInt(container.style.top)||0;handle.style.cursor='grabbing';e.preventDefault();});
document.addEventListener('mousemove',(e)=>{if(!d)return;container.style.left=Math.max(0,Math.min(window.innerWidth-container.offsetWidth,sl+e.clientX-sx))+'px';container.style.top=Math.max(0,Math.min(window.innerHeight-container.offsetHeight,st+e.clientY-sy))+'px';container.style.bottom='auto';container.style.right='auto';});
document.addEventListener('mouseup',()=>{if(!d)return;d=false;handle.style.cursor='grab';savePosition(parseInt(container.style.left),parseInt(container.style.top));});
handle.addEventListener('touchstart',(e)=>{if(e.target.id==='af-arrow')return;const t=e.touches[0];d=true;sx=t.clientX;sy=t.clientY;sl=parseInt(container.style.left)||0;st=parseInt(container.style.top)||0;e.preventDefault();},{passive:false});
document.addEventListener('touchmove',(e)=>{if(!d)return;const t=e.touches[0];container.style.left=Math.max(0,Math.min(window.innerWidth-container.offsetWidth,sl+t.clientX-sx))+'px';container.style.top=Math.max(0,Math.min(window.innerHeight-container.offsetHeight,st+t.clientY-sy))+'px';container.style.bottom='auto';container.style.right='auto';e.preventDefault();},{passive:false});
document.addEventListener('touchend',()=>{if(!d)return;d=false;savePosition(parseInt(container.style.left),parseInt(container.style.top));});
}
// =====================================================================
// UI
// =====================================================================
function mk(tag,styles,text){const e=document.createElement(tag);if(styles)Object.assign(e.style,styles);if(text!==undefined)e.textContent=text;return e;}
function inp(id,type,value,min,width){const e=document.createElement('input');e.id=id;e.type=type;e.value=value;if(min!==undefined)e.min=min;Object.assign(e.style,{width:width||'70px',padding:'3px 5px',borderRadius:'4px',border:'1px solid #555',backgroundColor:'#1a1a1a',color:'#fff',fontSize:'12px',boxSizing:'border-box'});return e;}
function buildTabSettings(body){
const s=document.createElement('div');s.id='af-tab-settings';
s.style.maxHeight='340px';s.style.overflowY='auto';s.style.paddingRight='4px';
const add=(label,children)=>{s.appendChild(mk('div',{color:'#aaa',fontSize:'12px',marginBottom:'2px'},label));const row=mk('div',{display:'flex',gap:'6px',alignItems:'center',marginBottom:'6px'});children.forEach(c=>row.appendChild(c));s.appendChild(row);};
const sep=()=>s.appendChild(mk('div',{borderTop:'1px solid #444',margin:'8px 0'}));
const sec=(t)=>s.appendChild(mk('div',{color:'#f0a500',fontWeight:'bold',fontSize:'12px',marginBottom:'6px'},t));
const addCb=(id,label,checked)=>{const w=mk('div',{display:'flex',alignItems:'center',gap:'6px',marginBottom:'5px'});const cb=document.createElement('input');cb.id=id;cb.type='checkbox';cb.checked=checked;const l=mk('label',{color:'#fff',fontSize:'12px',cursor:'pointer'},label);l.htmlFor=id;w.appendChild(cb);w.appendChild(l);s.appendChild(w);};
sec('đŸ FarmGod Einstellungen');
s.appendChild(mk('div',{color:'#aaa',fontSize:'12px',marginBottom:'2px'},'Gruppe:'));
const gw=mk('div',{marginBottom:'6px'});gw.id='af-group-wrapper';gw.innerHTML='<span style="color:#aaa;font-size:11px;">LĂ€dt...</span>';s.appendChild(gw);
FarmGodCore.buildGroupSelect(settings.optionGroup).then(html=>{gw.innerHTML=html;});
add('Standard-Reichweite (Felder):',[inp('af-distance','number',settings.optionDistance,1,'80px'),mk('span',{color:'#aaa'},'Felder')]);
add('Mindestzeit zwischen Farms:',[inp('af-time','number',settings.optionTime,0,'80px'),mk('span',{color:'#aaa'},'min')]);
addCb('af-losses','Farms mit Teilverlusten senden',settings.optionLosses);
addCb('af-maxloot','B-Farm bei vollem Loot senden',settings.optionMaxloot);
addCb('af-newbarbs','Neue Barbarendörfer hinzufĂŒgen',settings.optionNewbarbs);
sep();
sec('âšïž Enter-Einstellungen');
add('Reload-Intervall:',[inp('af-reload-min','number',settings.reloadMin,10,'65px'),mk('span',{color:'#aaa'},'â'),inp('af-reload-max','number',settings.reloadMax,10,'65px'),mk('span',{color:'#aaa'},'s')]);
add('Enter-Intervall (Einzel):',[inp('af-enter-min','number',settings.enterMin,50,'65px'),mk('span',{color:'#aaa'},'â'),inp('af-enter-max','number',settings.enterMax,50,'65px'),mk('span',{color:'#aaa'},'ms')]);
addCb('af-hold-enter','Enter gedrĂŒckt halten (Schnellmodus)',settings.holdEnter);
add('Wiederholrate (Hold):',[inp('af-hold-interval','number',settings.holdInterval,10,'65px'),mk('span',{color:'#666',fontSize:'11px'},'ms (OS Key-Repeat)')]);
sep();
sec('đ Discord');
s.appendChild(mk('div',{color:'#aaa',fontSize:'12px',marginBottom:'2px'},'Webhook URL:'));
const wi=inp('af-webhook-url','text',settings.webhookUrl||'',undefined,'100%');wi.placeholder='https://discord.com/api/webhooks/...';wi.style.marginBottom='8px';s.appendChild(wi);
sep();
sec('đ Tagesergebnis');
const lootTable=mk('div',{fontSize:'12px',marginBottom:'4px'});
const lootRes=mk('div',{color:'#27ae60',marginBottom:'2px'},'đŠ â');lootRes.id='af-loot-res';
const lootVil=mk('div',{color:'#3498db',marginBottom:'2px'},'đïž â');lootVil.id='af-loot-vil';
const lootAvg=mk('div',{color:'#e67e22',marginBottom:'4px'},'⥠â');lootAvg.id='af-loot-avg';
lootTable.appendChild(lootRes);lootTable.appendChild(lootVil);lootTable.appendChild(lootAvg);
s.appendChild(lootTable);
s.appendChild(mk('div',{color:'#666',fontSize:'10px'},'Aktualisierung nach jedem Loop'));
const saveBtn=mk('button',{display:'block',width:'100%',marginBottom:'8px',marginTop:'8px'},'Einstellungen speichern'); saveBtn.className='af-btn af-btn-primary';
saveBtn.addEventListener('click',()=>{
const sel=document.querySelector('#af-group-wrapper select');
const ns={
optionGroup:sel?parseInt(sel.value):settings.optionGroup,
optionDistance:Math.max(1,parseFloat(document.getElementById('af-distance').value)||DEFAULTS.optionDistance),
optionTime:Math.max(0,parseFloat(document.getElementById('af-time').value)||DEFAULTS.optionTime),
optionLosses:document.getElementById('af-losses').checked,
optionMaxloot:document.getElementById('af-maxloot').checked,
optionNewbarbs:document.getElementById('af-newbarbs').checked,
villageDistances:loadSettings().villageDistances,
villageSettings:loadSettings().villageSettings,
enterMin:Math.max(50,parseInt(document.getElementById('af-enter-min').value)||DEFAULTS.enterMin),
enterMax:Math.max(50,parseInt(document.getElementById('af-enter-max').value)||DEFAULTS.enterMax),
reloadMin:Math.max(10,parseInt(document.getElementById('af-reload-min').value)||DEFAULTS.reloadMin),
reloadMax:Math.max(10,parseInt(document.getElementById('af-reload-max').value)||DEFAULTS.reloadMax),
holdEnter:document.getElementById('af-hold-enter').checked,
holdInterval:Math.max(10,parseInt(document.getElementById('af-hold-interval').value)||DEFAULTS.holdInterval),
webhookUrl:document.getElementById('af-webhook-url').value.trim(),
};
if(ns.enterMin>ns.enterMax)[ns.enterMin,ns.enterMax]=[ns.enterMax,ns.enterMin];
if(ns.reloadMin>ns.reloadMax)[ns.reloadMin,ns.reloadMax]=[ns.reloadMax,ns.reloadMin];
saveSettings(ns);settings=ns;
saveBtn.textContent='â Gespeichert!';setTimeout(()=>{saveBtn.textContent='Einstellungen speichern';},1500);
});
s.appendChild(saveBtn);
body.appendChild(s);
}
// Gecachte Dorfliste fĂŒr Village-Tab
let cachedVillages = {};
function buildTabVillages(body) {
const s = document.createElement('div');
s.id = 'af-tab-villages';
s.style.display = 'none';
// Header mit + Button
const header = mk('div', {display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:'8px'});
header.appendChild(mk('div', {color:'#aaa', fontSize:'11px'}, 'Dörfer mit individuellen Einstellungen:'));
const addBtn = mk('button', {border:'none', borderRadius:'50%', width:'22px', height:'22px', backgroundColor:'#27ae60', color:'#fff', fontSize:'16px', cursor:'pointer', lineHeight:'20px', padding:'0', flexShrink:'0'}, '+');
header.appendChild(addBtn);
s.appendChild(header);
// Liste der konfigurierten Dörfer
const configList = mk('div', {maxHeight:'300px', overflowY:'auto'});
configList.id = 'af-vs-config-list';
s.appendChild(configList);
// Modal Overlay
// Altes Modal entfernen falls vorhanden
const existingModal = document.getElementById('af-vs-modal');
if (existingModal) existingModal.remove();
const modal = mk('div', {
display:'none', position:'fixed', top:'0', left:'0', width:'100%', height:'100%',
backgroundColor:'rgba(0,0,0,0.7)', zIndex:'20000', justifyContent:'center', alignItems:'center'
});
modal.id = 'af-vs-modal';
const modalBox = mk('div', {
backgroundColor:'#2c2c2c', borderRadius:'8px', padding:'16px', minWidth:'260px', maxWidth:'300px',
maxHeight:'80vh', overflowY:'auto', color:'#fff', fontFamily:'Arial, sans-serif', fontSize:'13px',
position:'relative', margin:'auto', top:'50%', transform:'translateY(-50%)'
});
// Modal Titel
const modalTitle = mk('div', {fontWeight:'bold', fontSize:'14px', marginBottom:'12px', borderBottom:'1px solid #555', paddingBottom:'6px'}, 'Dorf konfigurieren');
modalBox.appendChild(modalTitle);
// Dorf-Auswahl Dropdown
modalBox.appendChild(mk('div', {color:'#aaa', fontSize:'12px', marginBottom:'2px'}, 'Dorf:'));
const villageSelect = document.createElement('select');
villageSelect.id = 'af-vs-modal-village';
Object.assign(villageSelect.style, {width:'100%', backgroundColor:'#1a1a1a', color:'#fff', border:'1px solid #555', borderRadius:'4px', padding:'4px', fontSize:'12px', marginBottom:'10px', boxSizing:'border-box'});
modalBox.appendChild(villageSelect);
// Modus
modalBox.appendChild(mk('div', {color:'#aaa', fontSize:'12px', marginBottom:'2px'}, 'Modus:'));
const modeSelect = document.createElement('select');
modeSelect.id = 'af-vs-modal-mode';
Object.assign(modeSelect.style, {width:'100%', backgroundColor:'#1a1a1a', color:'#fff', border:'1px solid #555', borderRadius:'4px', padding:'4px', fontSize:'12px', marginBottom:'10px', boxSizing:'border-box'});
[['distance','Nur Distanz'], ['area','Nur Bereich'], ['both','Distanz + Bereich']].forEach(([val, label]) => {
const opt = document.createElement('option'); opt.value = val; opt.textContent = label;
modeSelect.appendChild(opt);
});
modalBox.appendChild(modeSelect);
// Distanz-Zeile
const distRow = mk('div', {marginBottom:'10px'});
distRow.id = 'af-vs-modal-distrow';
distRow.appendChild(mk('div', {color:'#aaa', fontSize:'12px', marginBottom:'2px'}, 'Reichweite:'));
const distRowInner = mk('div', {display:'flex', alignItems:'center', gap:'6px'});
const distInp = inp('af-vs-modal-dist', 'number', settings.optionDistance, 1, '80px');
distRowInner.appendChild(distInp);
distRowInner.appendChild(mk('span', {color:'#aaa', fontSize:'12px'}, 'Felder'));
distRow.appendChild(distRowInner);
modalBox.appendChild(distRow);
// Bereich
const areaDiv = mk('div', {marginBottom:'10px'});
areaDiv.id = 'af-vs-modal-areadiv';
areaDiv.appendChild(mk('div', {color:'#aaa', fontSize:'12px', marginBottom:'4px'}, 'Farmbereich:'));
const makeCoordRow = (label, xId, yId) => {
const row = mk('div', {display:'flex', alignItems:'center', gap:'4px', marginBottom:'4px'});
row.appendChild(mk('span', {color:'#aaa', fontSize:'11px', minWidth:'28px'}, label));
const xi = inp(xId, 'number', 0, 0, '60px'); xi.placeholder = 'X';
const yi = inp(yId, 'number', 0, 0, '60px'); yi.placeholder = 'Y';
row.appendChild(xi);
row.appendChild(mk('span', {color:'#555', fontSize:'12px'}, '|'));
row.appendChild(yi);
return row;
};
areaDiv.appendChild(makeCoordRow('Von:', 'af-vs-modal-x1', 'af-vs-modal-y1'));
areaDiv.appendChild(makeCoordRow('Bis:', 'af-vs-modal-x2', 'af-vs-modal-y2'));
modalBox.appendChild(areaDiv);
// Modus-Visibility
const updateModalVisibility = () => {
const mode = modeSelect.value;
distRow.style.display = (mode === 'distance' || mode === 'both') ? 'block' : 'none';
areaDiv.style.display = (mode === 'area' || mode === 'both') ? 'block' : 'none';
};
modeSelect.addEventListener('change', updateModalVisibility);
updateModalVisibility();
// Buttons
const btnRow = mk('div', {display:'flex', gap:'8px', marginTop:'4px'});
const saveModalBtn = mk('button', {flex:'1', padding:'6px', border:'none', borderRadius:'4px', cursor:'pointer', backgroundColor:'#27ae60', color:'#fff', fontSize:'12px', fontWeight:'bold'}, 'Speichern');
const cancelModalBtn = mk('button', {flex:'1', padding:'6px', border:'none', borderRadius:'4px', cursor:'pointer', backgroundColor:'#555', color:'#fff', fontSize:'12px'}, 'Abbrechen');
btnRow.appendChild(saveModalBtn);
btnRow.appendChild(cancelModalBtn);
modalBox.appendChild(btnRow);
modal.appendChild(modalBox);
document.body.appendChild(modal);
// Modal schlieĂen
cancelModalBtn.addEventListener('click', () => { modal.style.display = 'none'; });
modal.addEventListener('click', (e) => { if (e.target === modal) modal.style.display = 'none'; });
// Konfigurations-Liste neu rendern
function renderConfigList() {
configList.innerHTML = '';
settings = loadSettings();
const vs = settings.villageSettings || {};
const entries = Object.entries(vs);
if (entries.length === 0) {
configList.appendChild(mk('div', {color:'#666', fontSize:'11px', padding:'8px 0'}, 'Keine individuellen Einstellungen vorhanden.'));
return;
}
entries.forEach(([coord, cfg]) => {
const vName = cachedVillages[coord] ? cachedVillages[coord].name : coord;
const row = mk('div', {
display:'flex', alignItems:'center', gap:'6px', marginBottom:'6px',
padding:'5px', borderRadius:'4px', backgroundColor:'#1e1e1e', border:'1px solid #333'
});
// Info
const info = mk('div', {flex:'1', overflow:'hidden'});
info.appendChild(mk('div', {fontSize:'11px', color:'#ccc', whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis'}, `${vName} (${coord})`));
const modeLabels = {distance:'Distanz', area:'Bereich', both:'Distanz + Bereich'};
let detail = modeLabels[cfg.mode] || cfg.mode;
if ((cfg.mode === 'distance' || cfg.mode === 'both') && cfg.distance) detail += ` · ${cfg.distance} Felder`;
if (cfg.mode === 'area' || cfg.mode === 'both') detail += ` · (${cfg.x1}|${cfg.y1})-(${cfg.x2}|${cfg.y2})`;
info.appendChild(mk('div', {fontSize:'10px', color:'#888', marginTop:'2px'}, detail));
row.appendChild(info);
// Bearbeiten
const editBtn = mk('button', {border:'none', borderRadius:'4px', padding:'3px 7px', cursor:'pointer', backgroundColor:'#2980b9', color:'#fff', fontSize:'11px'}, 'âïž');
editBtn.addEventListener('click', () => openModal(coord, cfg));
row.appendChild(editBtn);
// Löschen
const delBtn = mk('button', {border:'none', borderRadius:'4px', padding:'3px 7px', cursor:'pointer', backgroundColor:'#c0392b', color:'#fff', fontSize:'11px'}, 'đ');
delBtn.addEventListener('click', () => {
settings = loadSettings();
delete settings.villageSettings[coord];
delete settings.villageDistances[coord];
saveSettings(settings);
renderConfigList();
});
row.appendChild(delBtn);
configList.appendChild(row);
});
}
// Modal öffnen (neu oder bearbeiten)
function openModal(editCoord, existingCfg) {
// Dorf-Dropdown befĂŒllen
villageSelect.innerHTML = '';
settings = loadSettings();
const usedCoords = Object.keys(settings.villageSettings || {});
if (Object.keys(cachedVillages).length === 0) {
// Dörfer noch nicht geladen
const loadingOpt = document.createElement('option');
loadingOpt.textContent = 'âł Wird geladen...';
loadingOpt.disabled = true;
villageSelect.appendChild(loadingOpt);
FarmGodCore.loadVillages(settings.optionGroup).then(villages => {
cachedVillages = villages;
openModal(editCoord, existingCfg);
});
modal.style.display = 'flex';
return;
}
Object.entries(cachedVillages).sort(([,a],[,b])=>a.name.localeCompare(b.name)).forEach(([coord, v]) => {
// Beim Bearbeiten: alle anzeigen; beim Neuanlegen: bereits konfigurierte ausblenden
if (!editCoord && usedCoords.includes(coord)) return;
const opt = document.createElement('option');
opt.value = coord;
opt.textContent = `${v.name} (${coord})`;
if (coord === editCoord) opt.selected = true;
villageSelect.appendChild(opt);
});
if (villageSelect.options.length === 0) {
const opt = document.createElement('option');
opt.textContent = 'Alle Dörfer bereits konfiguriert';
opt.disabled = true;
villageSelect.appendChild(opt);
}
// Felder vorausfĂŒllen wenn bearbeiten
if (existingCfg) {
modeSelect.value = existingCfg.mode || 'distance';
distInp.value = existingCfg.distance || settings.optionDistance;
document.getElementById('af-vs-modal-x1').value = existingCfg.x1 !== undefined ? existingCfg.x1 : 0;
document.getElementById('af-vs-modal-y1').value = existingCfg.y1 !== undefined ? existingCfg.y1 : 0;
document.getElementById('af-vs-modal-x2').value = existingCfg.x2 !== undefined ? existingCfg.x2 : 999;
document.getElementById('af-vs-modal-y2').value = existingCfg.y2 !== undefined ? existingCfg.y2 : 999;
} else {
modeSelect.value = 'distance';
distInp.value = settings.optionDistance;
document.getElementById('af-vs-modal-x1').value = 0;
document.getElementById('af-vs-modal-y1').value = 0;
document.getElementById('af-vs-modal-x2').value = 999;
document.getElementById('af-vs-modal-y2').value = 999;
}
updateModalVisibility();
villageSelect.disabled = !!editCoord;
modal.style.display = 'flex';
}
// Speichern aus Modal
saveModalBtn.addEventListener('click', () => {
const coord = villageSelect.value;
if (!coord) return;
const mode = modeSelect.value;
const entry = { mode };
if (mode === 'distance' || mode === 'both') {
const d = parseFloat(distInp.value);
if (!isNaN(d) && d > 0) entry.distance = d;
}
if (mode === 'area' || mode === 'both') {
entry.x1 = parseFloat(document.getElementById('af-vs-modal-x1').value) || 0;
entry.y1 = parseFloat(document.getElementById('af-vs-modal-y1').value) || 0;
entry.x2 = parseFloat(document.getElementById('af-vs-modal-x2').value) || 999;
entry.y2 = parseFloat(document.getElementById('af-vs-modal-y2').value) || 999;
}
settings = loadSettings();
if (!settings.villageSettings) settings.villageSettings = {};
settings.villageSettings[coord] = entry;
// villageDistances sync
if (!settings.villageDistances) settings.villageDistances = {};
if ((mode === 'distance' || mode === 'both') && entry.distance) {
settings.villageDistances[coord] = entry.distance;
} else {
delete settings.villageDistances[coord];
}
saveSettings(settings);
modal.style.display = 'none';
renderConfigList();
});
// + Button öffnet Modal
addBtn.addEventListener('click', () => openModal(null, null));
// Beim Tab-Klick Dörfer im Hintergrund laden
if (tabBtn) {
tabBtn.addEventListener('click', () => {
if (Object.keys(cachedVillages).length === 0) {
settings = loadSettings();
FarmGodCore.loadVillages(settings.optionGroup).then(v => { cachedVillages = v; });
}
});
}
// Dörfer im Hintergrund laden, dann Liste rendern
function loadAndRender() {
settings = loadSettings();
if (Object.keys(cachedVillages).length === 0) {
FarmGodCore.loadVillages(settings.optionGroup).then(v => {
cachedVillages = v;
renderConfigList();
}).catch(() => renderConfigList()); // Fallback: Koordinaten zeigen
} else {
renderConfigList();
}
}
setTimeout(() => loadAndRender(), 100);
// Custom Event fĂŒr switchTab
document.addEventListener('af-render-config-list', () => loadAndRender());
body.appendChild(s);
}
function switchTab(tab){
['settings','katta'].forEach(t=>{
const el=document.getElementById(`af-tab-${t}`),btn=document.getElementById(`af-tabbt-${t}`);
if(el)el.style.display=t===tab?'block':'none';
if(btn)btn.className='af-tab-btn'+(t===tab?' active':'');
});
// Bij wisselen naar dörfer tab: lijst opnieuw renderen
if(tab==='villages'||tab==='night'){
const cl=document.getElementById('af-vs-config-list');
if(cl){
const s=loadSettings();
const vs=s.villageSettings||{};
cl.innerHTML='';
const entries=Object.entries(vs);
if(entries.length===0){
cl.appendChild(document.createElement('div')).innerText='Keine individuellen Einstellungen vorhanden.';
cl.firstChild.style.cssText='color:#666;font-size:11px;padding:8px 0';
} else {
// renderConfigList existiert im closure von buildTabVillages
// daher rufen wir es ĂŒber ein Custom Event auf
document.dispatchEvent(new CustomEvent('af-render-config-list'));
}
}
}
}
function initializeUI(){
const existing=document.getElementById('af-container');if(existing)existing.remove();
// ââ CSS-Variablen & globale Styles ââââââââââââââââââââââââââââââ
if (!document.getElementById('af-styles')) {
const style = document.createElement('style');
style.id = 'af-styles';
style.textContent = `
#af-container * { box-sizing: border-box; }
#af-container { --bg0: #0d1117; --bg1: #161b22; --bg2: #21262d; --bg3: #30363d;
--gold: #c9a84c; --gold-dim: #8a6f2e; --blue: #4a9eff; --green: #3fb950;
--red: #f85149; --orange: #f0883e; --text: #e6edf3; --muted: #8b949e; }
#af-container input[type=number], #af-container input[type=text],
#af-container select {
background: var(--bg0) !important; color: var(--text) !important;
border: 1px solid var(--bg3) !important; border-radius: 6px !important;
padding: 5px 8px !important; font-size: 12px !important;
transition: border-color .15s;
}
#af-container input:focus, #af-container select:focus {
outline: none !important; border-color: var(--blue) !important;
}
#af-container input[type=checkbox] { accent-color: var(--gold); width:14px; height:14px; }
.af-btn {
border: none; border-radius: 6px; cursor: pointer; font-weight: 600;
font-size: 12px; padding: 6px 12px; transition: opacity .15s, transform .1s;
letter-spacing: .3px;
}
.af-btn:hover { opacity: .85; }
.af-btn:active { transform: scale(.97); }
.af-btn-primary { background: var(--blue); color: #fff; }
.af-btn-gold { background: linear-gradient(135deg, var(--gold), var(--gold-dim)); color: #0d1117; }
.af-btn-danger { background: var(--red); color: #fff; }
.af-btn-ghost { background: var(--bg3); color: var(--text); }
.af-tab-btn {
flex: 1; padding: 6px 8px; border: none; background: transparent;
color: var(--muted); font-size: 11px; font-weight: 600; cursor: pointer;
border-bottom: 2px solid transparent; transition: color .15s, border-color .15s;
letter-spacing: .4px; text-transform: uppercase;
}
.af-tab-btn.active { color: var(--gold); border-bottom-color: var(--gold); }
.af-section-label {
font-size: 10px; font-weight: 700; letter-spacing: 1px;
text-transform: uppercase; color: var(--muted); margin: 12px 0 6px;
}
.af-section-label:first-child { margin-top: 0; }
.af-divider { border: none; border-top: 1px solid var(--bg3); margin: 10px 0; }
.af-row { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.af-label { color: var(--muted); font-size: 12px; flex: 1; }
.af-pulse { display: inline-block; width: 7px; height: 7px; border-radius: 50%;
background: var(--green); box-shadow: 0 0 0 0 rgba(63,185,80,.4);
animation: af-pulse 2s infinite; }
.af-pulse.paused { background: var(--orange); box-shadow: none; animation: none; }
.af-pulse.stopped { background: var(--muted); box-shadow: none; animation: none; }
@keyframes af-pulse {
0% { box-shadow: 0 0 0 0 rgba(63,185,80,.4); }
70% { box-shadow: 0 0 0 6px rgba(63,185,80,0); }
100% { box-shadow: 0 0 0 0 rgba(63,185,80,0); }
}
.af-progress-bar { height: 3px; background: var(--bg3); border-radius: 2px; overflow: hidden; margin: 4px 0 8px; }
.af-progress-fill { height: 100%; background: linear-gradient(90deg, var(--gold), var(--blue));
border-radius: 2px; transition: width .4s ease; width: 0%; }
`;
document.head.appendChild(style);
}
const mk2 = (tag, styles, text) => {
const e = document.createElement(tag);
if (styles) Object.assign(e.style, styles);
if (text !== undefined) e.textContent = text;
return e;
};
const container = mk('div', {
backgroundColor: 'var(--bg1)', color: 'var(--text)',
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
fontSize: '13px', margin: '0 0 10px 0', borderRadius: '10px',
boxShadow: '0 4px 24px rgba(0,0,0,.6)', overflow: 'hidden',
border: '1px solid var(--bg3)'
});
container.id = 'af-container';
// ââ Header âââââââââââââââââââââââââââââââââââââââââââââââââââââââ
const titleBar = mk('div', {
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
padding: '10px 14px', backgroundColor: 'var(--bg0)',
borderBottom: '1px solid var(--bg3)', userSelect: 'none', cursor: 'pointer'
});
const titleLeft = mk('div', { display: 'flex', alignItems: 'center', gap: '10px' });
// Logo + Name
const logoWrap = mk('div', { display: 'flex', alignItems: 'center', gap: '8px' });
const pulse = mk('div'); pulse.className = 'af-pulse' + (isRunning ? '' : ' stopped');
pulse.id = 'af-pulse';
const nameEl = mk('span', { fontWeight: '700', fontSize: '14px', letterSpacing: '.5px',
background: 'linear-gradient(90deg, var(--gold), #e8c97a)', WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent' }, 'Anaboles farmen');
logoWrap.appendChild(pulse);
logoWrap.appendChild(nameEl);
titleLeft.appendChild(logoWrap);
// Mini-Status
const miniStatus = mk('div', { display: isMinimized ? 'flex' : 'none', alignItems: 'center', gap: '12px', fontSize: '12px' });
miniStatus.id = 'af-mini-status';
const statusMini = mk('span', { color: 'var(--gold)', fontWeight: '600' }, currentStatus); statusMini.id = 'af-status-mini';
const timerMini = mk('span', { color: 'var(--muted)' }, currentCountdownText); timerMini.id = 'af-timer-mini';
const modeMini = mk('span', { fontSize: '11px' }, ''); modeMini.id = 'af-mode-mini';
const lootMini = mk('span', { color: 'var(--green)', fontSize: '11px' }, ''); lootMini.id = 'af-loot-mini';
miniStatus.appendChild(statusMini);
miniStatus.appendChild(timerMini);
miniStatus.appendChild(modeMini);
miniStatus.appendChild(lootMini);
const miniResumeBtn = mk('button', {
display: captchaAlertSent ? 'inline-block' : 'none',
marginLeft: '8px', padding: '2px 8px', border: 'none', borderRadius: '4px',
cursor: 'pointer', color: '#fff', fontSize: '11px', backgroundColor: 'var(--orange)'
}, 'â¶ Fortsetzen');
miniResumeBtn.id = 'af-mini-resume-btn';
miniResumeBtn.addEventListener('click', (e) => { e.stopPropagation(); resumeAfterCaptcha(); });
miniStatus.appendChild(miniResumeBtn);
titleLeft.appendChild(miniStatus);
titleBar.appendChild(titleLeft);
const titleRight = mk('div', { display: 'flex', alignItems: 'center', gap: '6px' });
const miniToggleBtn = mk('button', {
display: isMinimized ? 'block' : 'none',
padding: '3px 10px', border: 'none', borderRadius: '4px',
cursor: 'pointer', color: '#0d1117', fontSize: '11px', fontWeight: '700',
background: 'linear-gradient(135deg, var(--gold), var(--gold-dim))'
});
miniToggleBtn.id = 'af-mini-toggle-btn';
miniToggleBtn.addEventListener('click', (e) => { e.stopPropagation(); toggleProcess(); });
titleRight.appendChild(miniToggleBtn);
const arrowBtn = mk('button', {
background: 'none', border: 'none', color: 'var(--muted)',
cursor: 'pointer', fontSize: '12px', padding: '2px 4px',
borderRadius: '4px', transition: 'color .15s'
}, isMinimized ? 'âŒ' : 'âČ');
arrowBtn.id = 'af-arrow';
arrowBtn.addEventListener('click', (e) => { e.stopPropagation(); toggleMinimize(); });
titleRight.appendChild(arrowBtn);
titleBar.appendChild(titleRight);
titleBar.addEventListener('click', () => toggleMinimize());
container.appendChild(titleBar);
// ââ Body âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
const body = mk('div', { display: isMinimized ? 'none' : 'block', padding: '12px 14px' });
body.id = 'af-body';
// Progress bar (dekorativ, zeigt Farm-Fortschritt)
const progressWrap = mk('div'); progressWrap.className = 'af-progress-bar';
const progressFill = mk('div'); progressFill.className = 'af-progress-fill'; progressFill.id = 'af-progress-fill';
progressWrap.appendChild(progressFill);
body.appendChild(progressWrap);
const countdown = mk('div', { display: 'none', marginBottom: '8px', color: 'var(--gold)', fontWeight: '600', fontSize: '12px' });
countdown.id = 'af-countdown'; body.appendChild(countdown);
// Tabs
const tabBar = mk('div', {
display: 'flex', gap: '0', marginBottom: '12px',
borderBottom: '1px solid var(--bg3)'
});
[['settings', 'â Einstellungen'], ['katta', 'â Rammen']].forEach(([id, label]) => {
const btn = mk('button', {}, label);
btn.className = 'af-tab-btn';
btn.id = `af-tabbt-${id}`;
btn.addEventListener('click', () => switchTab(id));
tabBar.appendChild(btn);
});
body.appendChild(tabBar);
buildTabSettings(body);
buildTabKatta(body);
// Start/Stop Button
const controlButton = mk('button', { display: 'block', width: '100%', padding: '8px', marginTop: '10px' });
controlButton.className = 'af-btn af-btn-gold';
controlButton.id = 'af-toggle-btn';
controlButton.addEventListener('click', toggleProcess);
body.appendChild(controlButton);
const resumeButton = mk('button', {
display: captchaAlertSent ? 'block' : 'none',
width: '100%', padding: '8px', marginTop: '6px'
}, 'ⶠFortsetzen (Captcha gelöst)');
resumeButton.className = 'af-btn af-btn-ghost';
resumeButton.id = 'af-resume-btn';
resumeButton.addEventListener('click', resumeAfterCaptcha);
body.appendChild(resumeButton);
container.appendChild(body);
const farmWidget = document.getElementById('am_widget_Farm');
if (farmWidget) {
farmWidget.parentNode.insertBefore(container, farmWidget);
} else {
document.body.appendChild(container);
}
updateButtonState();
['settings', 'katta'].forEach(t => {
const btn = document.getElementById(`af-tabbt-${t}`);
if (btn) btn.className = 'af-tab-btn' + (t === 'settings' ? ' active' : '');
});
}
function buildTabKatta(body) {
const s = document.createElement('div');
s.id = 'af-tab-katta';
s.style.display = 'none';
s.style.maxHeight = '340px';
s.style.overflowY = 'auto';
s.style.paddingRight = '4px';
const mk2 = (tag, styles, text) => { const e = document.createElement(tag); if(styles)Object.assign(e.style,styles); if(text!==undefined)e.textContent=text; return e; };
const sec = (t) => { const d=mk2('div',{}); d.className='af-section-label'; d.textContent=t; s.appendChild(d); };
const sep = () => { const d=document.createElement('hr'); d.className='af-divider'; s.appendChild(d); };
const addCb = (id, label, checked) => {
const w = mk2('div',{display:'flex',alignItems:'center',gap:'6px',marginBottom:'5px'});
const cb = document.createElement('input'); cb.id=id; cb.type='checkbox'; cb.checked=checked;
const l = mk2('label',{color:'#fff',fontSize:'12px',cursor:'pointer'},label); l.htmlFor=id;
w.appendChild(cb); w.appendChild(l); s.appendChild(w);
};
const addRow = (label, children) => {
s.appendChild(mk2('div',{color:'#aaa',fontSize:'12px',marginBottom:'2px'},label));
const row = mk2('div',{display:'flex',gap:'6px',alignItems:'center',marginBottom:'6px'});
children.forEach(c=>row.appendChild(c)); s.appendChild(row);
};
const numInp = (id, val, w) => {
const e = document.createElement('input'); e.id=id; e.type='number'; e.value=val; e.min=0;
Object.assign(e.style,{width:w||'60px',padding:'3px 5px',borderRadius:'4px',border:'1px solid #555',backgroundColor:'#1a1a1a',color:'#fff',fontSize:'12px',boxSizing:'border-box'});
return e;
};
const st = loadSettings();
sec('đš Rammen bei roten/gelben Berichten');
addCb('katta-enabled', 'Aktiviert', st.kattaEnabled);
addCb('katta-on-red', 'Bei roten Berichten', st.kattaOnRed);
addCb('katta-on-yellow', 'Bei gelben Berichten', st.kattaOnYellow);
sep();
sec('đïž Angreifende Gruppe');
const gw = mk2('div',{marginBottom:'6px'}); gw.id='katta-group-wrapper';
gw.innerHTML='<span style="color:#aaa;font-size:11px;">LĂ€dt...</span>';
s.appendChild(gw);
FarmGodCore.buildGroupSelect(st.kattaGroup).then(html=>{gw.innerHTML=html;});
sep();
sec('âïž Truppenanzahl');
addRow('AxtkÀmpfer:', [numInp('katta-axe', st.kattaAxe)]);
addRow('Rammbock:', [numInp('katta-ram', st.kattaRam)]);
addRow('SpÀher:', [numInp('katta-spy', st.kattaSpy)]);
addRow('Katapult:', [numInp('katta-cata', st.kattaCata)]);
sep();
sec('đŻ Katapult-ZielgebĂ€ude');
const buildingSelect = document.createElement('select');
Object.assign(buildingSelect.style, {width:'100%',backgroundColor:'#1a1a1a',color:'#fff',border:'1px solid #555',borderRadius:'4px',padding:'4px',fontSize:'12px',marginBottom:'10px',boxSizing:'border-box'});
buildingSelect.id = 'katta-building';
const buildings = [
['wall','Wall'],['main','HauptgebÀude'],['barracks','Kaserne'],
['stable','Stall'],['garage','Werkstatt'],['market','Marktplatz'],
['wood','HolzfÀller'],['stone','Lehmgrube'],['iron','Eisenmine'],
['farm','Bauernhof'],['storage','Speicher'],['hide','Versteck'],
];
buildings.forEach(([val, label]) => {
const o = document.createElement('option'); o.value=val; o.textContent=label;
if (val === st.kattaTarget) o.selected=true;
buildingSelect.appendChild(o);
});
s.appendChild(buildingSelect);
sep();
// Gesendete Koordinaten anzeigen
const sentInfo = mk2('div',{color:'#666',fontSize:'10px',marginBottom:'6px'});
const sent = kattaLoadSent();
const sentCount = Object.keys(sent).length;
sentInfo.textContent = `Gespeicherte Katta-Logs: ${sentCount} Dörfer`;
s.appendChild(sentInfo);
const clearBtn = mk2('button',{display:'block',width:'100%',marginBottom:'8px'},'đ Ramm-Log löschen'); clearBtn.className='af-btn af-btn-danger';
clearBtn.addEventListener('click', () => {
localStorage.removeItem(KATTA_SENT_KEY);
sentInfo.textContent = 'Gespeicherte Katta-Logs: 0 Dörfer';
});
s.appendChild(clearBtn);
const saveBtn = mk2('button',{display:'block',width:'100%'},'Einstellungen speichern'); saveBtn.className='af-btn af-btn-primary';
saveBtn.addEventListener('click', () => {
const sel = document.querySelector('#katta-group-wrapper select');
const ns = Object.assign(loadSettings(), {
kattaEnabled: document.getElementById('katta-enabled').checked,
kattaOnRed: document.getElementById('katta-on-red').checked,
kattaOnYellow: document.getElementById('katta-on-yellow').checked,
kattaGroup: sel ? parseInt(sel.value) : st.kattaGroup,
kattaAxe: parseInt(document.getElementById('katta-axe').value) || 0,
kattaRam: parseInt(document.getElementById('katta-ram').value) || 0,
kattaSpy: parseInt(document.getElementById('katta-spy').value) || 0,
kattaCata: parseInt(document.getElementById('katta-cata').value) || 0,
kattaTarget: document.getElementById('katta-building').value,
});
saveSettings(ns); settings = ns;
saveBtn.textContent = 'â Gespeichert!';
setTimeout(()=>{ saveBtn.textContent='Einstellungen speichern'; }, 1500);
});
s.appendChild(saveBtn);
body.appendChild(s);
}
// =====================================================================
// START
// =====================================================================
function buildTabNight(body) {
const s = document.createElement('div');
s.id = 'af-tab-night';
s.style.display = 'none';
const sep = () => s.appendChild(mk('div', {borderTop:'1px solid #444', margin:'8px 0'}));
const sec = (t) => s.appendChild(mk('div', {color:'#f0a500', fontWeight:'bold', fontSize:'12px', marginBottom:'6px'}, t));
const addCb = (id, label, checked) => {
const w = mk('div', {display:'flex', alignItems:'center', gap:'6px', marginBottom:'6px'});
const cb = document.createElement('input'); cb.id = id; cb.type = 'checkbox'; cb.checked = checked;
const l = mk('label', {color:'#fff', fontSize:'12px', cursor:'pointer'}, label); l.htmlFor = id;
w.appendChild(cb); w.appendChild(l); s.appendChild(w);
};
const add = (label, children) => {
s.appendChild(mk('div', {color:'#aaa', fontSize:'12px', marginBottom:'2px'}, label));
const row = mk('div', {display:'flex', gap:'6px', alignItems:'center', marginBottom:'6px'});
children.forEach(c => row.appendChild(c));
s.appendChild(row);
};
// Nachtmodus
sec('đ Nachtmodus');
addCb('af-night-enabled', 'Nachtmodus aktivieren', settings.nightModeEnabled);
add('Nacht von:', [
inp('af-night-start', 'time', settings.nightStart, undefined, '90px'),
mk('span', {color:'#aaa'}, 'bis'),
inp('af-night-end', 'time', settings.nightEnd, undefined, '90px')
]);
add('Reload-Intervall (Nacht):', [
inp('af-night-reload-min', 'number', settings.nightReloadMin, 10, '65px'),
mk('span', {color:'#aaa'}, 'â'),
inp('af-night-reload-max', 'number', settings.nightReloadMax, 10, '65px'),
mk('span', {color:'#aaa'}, 's')
]);
add('Mindestzeit zwischen Farms (Nacht):', [
inp('af-night-option-time', 'number', settings.nightOptionTime, 0, '80px'),
mk('span', {color:'#aaa'}, 'min')
]);
addCb('af-night-maxloot', 'B-Farm bei vollem Loot senden (Nacht)', settings.nightOptionMaxloot);
addCb('af-night-losses', 'Farms mit Teilverlusten senden (Nacht)', settings.nightOptionLosses);
// Nachtmodus Status
const nightStatusEl = mk('div', {fontSize:'11px', marginBottom:'6px', padding:'4px', borderRadius:'4px', backgroundColor:'#1e1e1e'});
nightStatusEl.id = 'af-night-status';
nightStatusEl.innerText = isNightMode() ? 'đ Nachtmodus aktiv' : 'âïž Tagmodus aktiv';
nightStatusEl.style.color = isNightMode() ? '#8e44ad' : '#f0a500';
s.appendChild(nightStatusEl);
sep();
// ZufÀllige Pausen
sec('➠ZufÀllige Pausen');
addCb('af-pause-enabled', 'ZufÀllige Pausen aktivieren', settings.randomPauseEnabled);
add('Pause alle:', [
inp('af-pause-interval-min', 'number', settings.randomPauseIntervalMin, 1, '55px'),
mk('span', {color:'#aaa'}, 'â'),
inp('af-pause-interval-max', 'number', settings.randomPauseIntervalMax, 1, '55px'),
mk('span', {color:'#aaa'}, 'Min')
]);
add('Pausendauer:', [
inp('af-pause-duration-min', 'number', settings.randomPauseDurationMin, 1, '55px'),
mk('span', {color:'#aaa'}, 'â'),
inp('af-pause-duration-max', 'number', settings.randomPauseDurationMax, 1, '55px'),
mk('span', {color:'#aaa'}, 'Min')
]);
sep();
const saveBtn = mk('button', {display:'block', width:'100%', padding:'5px', border:'none', borderRadius:'4px', cursor:'pointer', backgroundColor:'#2980b9', color:'#fff', fontSize:'12px'}, 'Einstellungen speichern');
saveBtn.addEventListener('click', () => {
const cur = loadSettings();
const ns = Object.assign({}, cur, {
nightModeEnabled: document.getElementById('af-night-enabled').checked,
nightStart: document.getElementById('af-night-start').value,
nightEnd: document.getElementById('af-night-end').value,
nightReloadMin: Math.max(10, parseInt(document.getElementById('af-night-reload-min').value) || DEFAULTS.nightReloadMin),
nightReloadMax: Math.max(10, parseInt(document.getElementById('af-night-reload-max').value) || DEFAULTS.nightReloadMax),
nightOptionTime: Math.max(0, parseFloat(document.getElementById('af-night-option-time').value) || DEFAULTS.nightOptionTime),
nightOptionMaxloot: document.getElementById('af-night-maxloot').checked,
nightOptionLosses: document.getElementById('af-night-losses').checked,
randomPauseEnabled: document.getElementById('af-pause-enabled').checked,
randomPauseIntervalMin: Math.max(1, parseInt(document.getElementById('af-pause-interval-min').value) || DEFAULTS.randomPauseIntervalMin),
randomPauseIntervalMax: Math.max(1, parseInt(document.getElementById('af-pause-interval-max').value) || DEFAULTS.randomPauseIntervalMax),
randomPauseDurationMin: Math.max(1, parseInt(document.getElementById('af-pause-duration-min').value) || DEFAULTS.randomPauseDurationMin),
randomPauseDurationMax: Math.max(1, parseInt(document.getElementById('af-pause-duration-max').value) || DEFAULTS.randomPauseDurationMax),
});
if (ns.nightReloadMin > ns.nightReloadMax) [ns.nightReloadMin, ns.nightReloadMax] = [ns.nightReloadMax, ns.nightReloadMin];
if (ns.randomPauseIntervalMin > ns.randomPauseIntervalMax) [ns.randomPauseIntervalMin, ns.randomPauseIntervalMax] = [ns.randomPauseIntervalMax, ns.randomPauseIntervalMin];
if (ns.randomPauseDurationMin > ns.randomPauseDurationMax) [ns.randomPauseDurationMin, ns.randomPauseDurationMax] = [ns.randomPauseDurationMax, ns.randomPauseDurationMin];
saveSettings(ns); settings = ns;
// Status aktualisieren
const el = document.getElementById('af-night-status');
if (el) { el.innerText = isNightMode() ? 'đ Nachtmodus aktiv' : 'âïž Tagmodus aktiv'; el.style.color = isNightMode() ? '#8e44ad' : '#f0a500'; }
saveBtn.textContent = 'â Gespeichert!';
setTimeout(() => { saveBtn.textContent = 'Einstellungen speichern'; }, 1500);
});
s.appendChild(saveBtn);
body.appendChild(s);
}
initializeUI();
startCaptchaObserver();
fetchTodayLoot();
if(isRunning)startProcess();
else setStatus('âč Bereit','#aaa');
})();