☰

πŸ’ͺ Anaboler Katta

Katapult-Angriffs-Planer fΓΌr Die StΓ€mme

이 μŠ€ν¬λ¦½νŠΈλŠ” 직접 μ„€μΉ˜ν•˜λŠ” μš©λ„κ°€ μ•„λ‹™λ‹ˆλ‹€. λ‹€λ₯Έ μŠ€ν¬λ¦½νŠΈμ—μ„œ 메타 μ§€μ‹œλ¬Έ // @require https://update.greasyfork.org/scripts/587599/1879981/%F0%9F%92%AA%20Anaboler%20Katta.js을(λ₯Ό) μ‚¬μš©ν•˜μ—¬ ν¬ν•¨ν•˜λŠ” λΌμ΄λΈŒλŸ¬λ¦¬μž…λ‹ˆλ‹€.

이 슀크립트λ₯Ό μ„€μΉ˜ν•˜λ €λ©΄ Tampermonkey, Greasemonkey λ˜λŠ” Violentmonkey와 같은 ν™•μž₯ ν”„λ‘œκ·Έλž¨μ΄ ν•„μš”ν•©λ‹ˆλ‹€.

이 슀크립트λ₯Ό μ„€μΉ˜ν•˜λ €λ©΄ Tampermonkey와 같은 ν™•μž₯ ν”„λ‘œκ·Έλž¨μ„ μ„€μΉ˜ν•΄μ•Ό ν•©λ‹ˆλ‹€.

이 슀크립트λ₯Ό μ„€μΉ˜ν•˜λ €λ©΄ Tampermonkey λ˜λŠ” Violentmonkey와 같은 ν™•μž₯ ν”„λ‘œκ·Έλž¨μ΄ ν•„μš”ν•©λ‹ˆλ‹€.

이 슀크립트λ₯Ό μ„€μΉ˜ν•˜λ €λ©΄ Tampermonkey λ˜λŠ” Userscripts와 같은 ν™•μž₯ ν”„λ‘œκ·Έλž¨μ΄ ν•„μš”ν•©λ‹ˆλ‹€.

이 슀크립트λ₯Ό μ„€μΉ˜ν•˜λ €λ©΄ Tampermonkey와 같은 ν™•μž₯ ν”„λ‘œκ·Έλž¨μ΄ ν•„μš”ν•©λ‹ˆλ‹€.

이 슀크립트λ₯Ό μ„€μΉ˜ν•˜λ €λ©΄ μœ μ € 슀크립트 κ΄€λ¦¬μž ν™•μž₯ ν”„λ‘œκ·Έλž¨μ΄ ν•„μš”ν•©λ‹ˆλ‹€.

(이미 μœ μ € 슀크립트 κ΄€λ¦¬μžκ°€ μ„€μΉ˜λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. μ„€μΉ˜λ₯Ό μ§„ν–‰ν•©λ‹ˆλ‹€!)

이 μŠ€νƒ€μΌμ„ μ„€μΉ˜ν•˜λ €λ©΄ Stylus와 같은 ν™•μž₯ ν”„λ‘œκ·Έλž¨μ΄ ν•„μš”ν•©λ‹ˆλ‹€.

이 μŠ€νƒ€μΌμ„ μ„€μΉ˜ν•˜λ €λ©΄ Stylus와 같은 ν™•μž₯ ν”„λ‘œκ·Έλž¨μ΄ ν•„μš”ν•©λ‹ˆλ‹€.

이 μŠ€νƒ€μΌμ„ μ„€μΉ˜ν•˜λ €λ©΄ Stylus와 같은 ν™•μž₯ ν”„λ‘œκ·Έλž¨μ΄ ν•„μš”ν•©λ‹ˆλ‹€.

이 μŠ€νƒ€μΌμ„ μ„€μΉ˜ν•˜λ €λ©΄ μœ μ € μŠ€νƒ€μΌ κ΄€λ¦¬μž ν™•μž₯ ν”„λ‘œκ·Έλž¨μ΄ ν•„μš”ν•©λ‹ˆλ‹€.

이 μŠ€νƒ€μΌμ„ μ„€μΉ˜ν•˜λ €λ©΄ μœ μ € μŠ€νƒ€μΌ κ΄€λ¦¬μž ν™•μž₯ ν”„λ‘œκ·Έλž¨μ΄ ν•„μš”ν•©λ‹ˆλ‹€.

이 μŠ€νƒ€μΌμ„ μ„€μΉ˜ν•˜λ €λ©΄ μœ μ € μŠ€νƒ€μΌ κ΄€λ¦¬μž ν™•μž₯ ν”„λ‘œκ·Έλž¨μ΄ ν•„μš”ν•©λ‹ˆλ‹€.

(이미 μœ μ € μŠ€νƒ€μΌ κ΄€λ¦¬μžκ°€ μ„€μΉ˜λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. μ„€μΉ˜λ₯Ό μ§„ν–‰ν•©λ‹ˆλ‹€!)

// ==UserScript==
// @name         πŸ’ͺ Anaboler Katta
// @namespace    http://tampermonkey.net/
// @version      2.2
// @description  Katapult-Angriffs-Planer fΓΌr Die StΓ€mme
// @match        https://*.die-staemme.de/game.php*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        unsafeWindow
// ==/UserScript==

(function () {
    'use strict';

    // Hub-Check: respektiert den Ein/Aus-Schalter im Anaboles Hub (id: 'kattaplaner')
    function isEnabledInHub() {
        try {
            const reg = JSON.parse(localStorage.getItem('intellifarm_registry') || '{}');
            return reg.kattaplaner !== false; // Default: aktiviert, falls Hub fehlt/Eintrag fehlt
        } catch { return true; }
    }
    if (!isEnabledInHub()) return;

    if (!window.location.href.includes('screen=overview_villages') ||
        !window.location.href.includes('mode=combined')) return;

    // Warte kurz bis Seite geladen
    setTimeout(init, 500);

    function init() {
        // ─── Styles ───────────────────────────────────────────────────────────
        const style = document.createElement('style');
        style.textContent = `
            #kp-panel {
                background: #f4e4bc;
                border: 2px solid #7a3e00;
                border-radius: 4px;
                margin-bottom: 10px;
                font-family: 'Trebuchet MS', sans-serif;
                font-size: 13px;
                color: #3b1e00;
                width: 100%;
                box-sizing: border-box;
            }
            #kp-header {
                background: #7a3e00;
                color: #f0d080;
                padding: 8px 14px;
                display: flex;
                justify-content: space-between;
                align-items: center;
                cursor: pointer;
                user-select: none;
                border-radius: 2px 2px 0 0;
                font-weight: bold;
                font-size: 14px;
            }
            #kp-header:hover { background: #9a5000; }
            #kp-toggle { font-size: 16px; }
            #kp-body { display: none; }
            #kp-body.open { display: block; }
            .kp-section {
                padding: 10px 14px;
                border-bottom: 1px solid #c8a96e;
            }
            .kp-section label {
                display: block;
                font-weight: bold;
                margin-bottom: 4px;
                font-size: 12px;
            }
            #kp-coords {
                width: 100%;
                height: 60px;
                background: #fffbe8;
                border: 1px solid #c8a96e;
                border-radius: 3px;
                padding: 6px;
                font-size: 12px;
                resize: vertical;
                box-sizing: border-box;
            }
            .kp-troops {
                display: flex;
                gap: 8px;
                flex-wrap: wrap;
                margin-top: 4px;
            }
            .kp-troop {
                display: flex;
                align-items: center;
                gap: 5px;
                background: #fffbe8;
                border: 1px solid #c8a96e;
                border-radius: 3px;
                padding: 4px 8px;
            }
            .kp-troop input[type=number] {
                width: 48px;
                background: #fffbe8;
                border: 1px solid #c8a96e;
                border-radius: 3px;
                padding: 2px 4px;
                font-size: 12px;
            }
            .kp-tpl-btn {
                padding: 3px 10px;
                background: #c8a050;
                color: #3b1e00;
                border: 1px solid #7a3e00;
                border-radius: 3px;
                cursor: pointer;
                font-size: 12px;
            }
            .kp-tpl-btn:hover { background: #e0b860; }
            .kp-tpl-btn.del { background: #8b2000; color: #f0d080; }
            .kp-tpl-btn.del:hover { background: #b03000; }
            .kp-tpl-name {
                padding: 3px 6px;
                border: 1px solid #c8a96e;
                border-radius: 3px;
                background: #fffbe8;
                font-size: 12px;
                width: 130px;
            }
            .kp-primary {
                padding: 6px 16px;
                background: #7a3e00;
                color: #f0d080;
                border: 1px solid #f0d080;
                border-radius: 3px;
                cursor: pointer;
                font-weight: bold;
                font-size: 13px;
            }
            .kp-primary:hover { background: #a05000; }
            .kp-error { color: #8b0000; font-size: 12px; margin-top: 4px; }
            .kp-info { font-size: 11px; color: #7a5a00; margin-top: 3px; }
            #kp-results { padding: 10px 14px; }
            #kp-results h3 {
                color: #7a3e00;
                margin: 0 0 8px 0;
                font-size: 13px;
            }
            .kp-attack-row {
                display: flex;
                align-items: center;
                gap: 8px;
                background: #fffbe8;
                border: 1px solid #c8a96e;
                border-radius: 3px;
                padding: 5px 8px;
                margin-bottom: 5px;
                flex-wrap: wrap;
            }
            .kp-attack-row span { flex: 1; font-size: 12px; min-width: 120px; }
            .kp-attack-btn {
                padding: 4px 10px;
                background: #3a6e00;
                color: #d0f080;
                border: 1px solid #d0f080;
                border-radius: 3px;
                cursor: pointer;
                font-size: 12px;
                white-space: nowrap;
            }
            .kp-attack-btn:hover { background: #508000; }
        `;
        document.head.appendChild(style);

        // ─── Panel einfΓΌgen ───────────────────────────────────────────────────
        const panel = document.createElement('div');
        panel.id = 'kp-panel';
        panel.innerHTML = `
            <div id="kp-header">
                πŸ’ͺ Anaboler Katta
                <span id="kp-toggle">β–Ό</span>
            </div>
            <div id="kp-body">
                <div class="kp-section">
                    <label>Koordinaten (Leerzeichen getrennt):</label>
                    <textarea id="kp-coords" placeholder="564|605 516|619 535|604 ..."></textarea>
                    <div class="kp-info" id="kp-coord-info"></div>
                </div>

                <div class="kp-section">
                    <label>Truppen pro Angriff:</label>
                    <div class="kp-troops" id="kp-troops">
                        ${TROOPS.map(t => `
                            <div class="kp-troop">
                                <input type="checkbox" id="kp-chk-${t.key}">
                                <label for="kp-chk-${t.key}" style="font-weight:normal">${t.label}</label>
                                <input type="number" id="kp-amt-${t.key}" value="1" min="0" max="9999">
                            </div>
                        `).join('')}
                    </div>
                </div>

                <div class="kp-section">
                    <label>Vorlagen:</label>
                    <div id="kp-templates-list" style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:8px;"></div>
                    <div style="display:flex;gap:8px;align-items:center;">
                        <input class="kp-tpl-name" type="text" id="kp-tpl-name" placeholder="Name der Vorlage">
                        <button class="kp-tpl-btn" id="kp-tpl-save">Speichern</button>
                    </div>
                </div>

                <div class="kp-section">
                    <label>Optionen:</label>
                    <div style="display:flex;gap:12px;flex-wrap:wrap;margin-top:4px;">
                        <div class="kp-troop">
                            <label for="kp-max-attacks" style="font-weight:normal">Max. Angriffe pro Dorf</label>
                            <input type="number" id="kp-max-attacks" value="" min="1" max="999" placeholder="unbegrenzt">
                        </div>
                        <div class="kp-troop">
                            <label for="kp-max-dist" style="font-weight:normal">Max. Entfernung</label>
                            <input type="number" id="kp-max-dist" value="" min="1" max="999" placeholder="unbegrenzt">
                        </div>
                    </div>
                </div>

                <div class="kp-section" style="display:flex;align-items:center;gap:12px;">
                    <button class="kp-primary" id="kp-calc">Angriffe berechnen</button>
                    <div class="kp-error" id="kp-error"></div>
                </div>

                <div id="kp-results"></div>
            </div>
        `;

        // In content_value einfΓΌgen (existiert auf jeder TW-Seite)
        const container = document.getElementById('content_value') ||
                          document.getElementById('game_body') ||
                          document.body;
        container.insertBefore(panel, container.firstChild);

        // ─── Events ───────────────────────────────────────────────────────────
        document.getElementById('kp-header').addEventListener('click', () => {
            const body = document.getElementById('kp-body');
            const toggle = document.getElementById('kp-toggle');
            body.classList.toggle('open');
            toggle.textContent = body.classList.contains('open') ? 'β–²' : 'β–Ό';
        });

        document.getElementById('kp-calc').addEventListener('click', calculate);
        document.getElementById('kp-tpl-save').addEventListener('click', saveTemplate);
        document.getElementById('kp-coords').addEventListener('input', updateCoordInfo);

        renderTemplates();
    }

    // ─── Truppen-Definitionen ─────────────────────────────────────────────────
    const TROOPS = [
        { key: 'spear',    label: 'Speer'   },
        { key: 'axe',      label: 'Axt'     },
        { key: 'spy',      label: 'SpΓ€her'  },
        { key: 'ram',      label: 'Ramme'   },
        { key: 'catapult', label: 'Kata'    },
    ];

    const COL_INDEX = { spear: 0, sword: 1, axe: 2, spy: 3, light: 4, heavy: 5, ram: 6, catapult: 7, snob: 8 };

    // ─── Truppen aus Tabelle lesen ────────────────────────────────────────────
    function readTroops() {
        const villages = [];
        const rows = document.querySelectorAll('#combined_table tbody tr.nowrap');
        rows.forEach(row => {
            const link = row.querySelector('td a[href*="village="]');
            if (!link) return;
            const idMatch = link.href.match(/village=(\d+)/);
            if (!idMatch) return;
            const villageId = idMatch[1];
            const villageName = link.textContent.trim();

            // Koordinaten aus Titeltext oder Link
            let vx = 0, vy = 0;
            const coordMatch = villageName.match(/\((\d+)\|(\d+)\)/) ||
                               (link.title || '').match(/\((\d+)\|(\d+)\)/);
            if (coordMatch) { vx = parseInt(coordMatch[1]); vy = parseInt(coordMatch[2]); }

            // Koordinaten aus Zeilen-Link falls nicht im Namen
            if (vx === 0) {
                const allLinks = row.querySelectorAll('a[href*="village="]');
                allLinks.forEach(l => {
                    const m = (l.textContent || '').match(/(\d+)\|(\d+)/);
                    if (m) { vx = parseInt(m[1]); vy = parseInt(m[2]); }
                });
            }

            const unitCells = row.querySelectorAll('td.unit-item');
            const troopData = {};
            TROOPS.forEach(t => {
                const idx = COL_INDEX[t.key];
                const cell = unitCells[idx];
                troopData[t.key] = cell ? parseInt(cell.textContent.trim()) || 0 : 0;
            });

            villages.push({ id: villageId, name: villageName, x: vx, y: vy, troops: troopData });
        });
        return villages;
    }

    // ─── Templates ────────────────────────────────────────────────────────────
    function loadTemplates() {
        try { return JSON.parse(GM_getValue('kp_templates', '{}')) || {}; } catch { return {}; }
    }
    function saveTemplates(t) { GM_setValue('kp_templates', JSON.stringify(t)); }

    function renderTemplates() {
        const container = document.getElementById('kp-templates-list');
        if (!container) return;
        const tpls = loadTemplates();
        const names = Object.keys(tpls);
        if (names.length === 0) {
            container.innerHTML = '<span style="font-size:12px;color:#7a5a00">Keine Vorlagen</span>';
            return;
        }
        container.innerHTML = names.map(n => `
            <button class="kp-tpl-btn" data-tpl="${n}">${n}</button>
            <button class="kp-tpl-btn del" data-del="${n}">βœ•</button>
        `).join('');
        container.querySelectorAll('[data-tpl]').forEach(b =>
            b.addEventListener('click', () => loadTemplate(b.dataset.tpl)));
        container.querySelectorAll('[data-del]').forEach(b =>
            b.addEventListener('click', () => deleteTemplate(b.dataset.del)));
    }

    function saveTemplate() {
        const name = document.getElementById('kp-tpl-name').value.trim();
        if (!name) return;
        const tpls = loadTemplates();
        tpls[name] = { coords: document.getElementById('kp-coords').value, troops: getSelectedTroops() };
        saveTemplates(tpls);
        renderTemplates();
        document.getElementById('kp-tpl-name').value = '';
    }

    function loadTemplate(name) {
        const tpl = loadTemplates()[name];
        if (!tpl) return;
        document.getElementById('kp-coords').value = tpl.coords || '';
        TROOPS.forEach(t => {
            const chk = document.getElementById(`kp-chk-${t.key}`);
            const amt = document.getElementById(`kp-amt-${t.key}`);
            if (chk) chk.checked = t.key in (tpl.troops || {});
            if (amt) amt.value = (tpl.troops || {})[t.key] || 1;
        });
        updateCoordInfo();
    }

    function deleteTemplate(name) {
        const tpls = loadTemplates();
        delete tpls[name];
        saveTemplates(tpls);
        renderTemplates();
    }

    // ─── Hilfe ────────────────────────────────────────────────────────────────
    function updateCoordInfo() {
        const coords = parseCoords();
        const info = document.getElementById('kp-coord-info');
        if (info) info.textContent = coords.length > 0 ? `${coords.length} Koordinaten erkannt` : '';
    }

    function parseCoords() {
        const raw = (document.getElementById('kp-coords').value || '').trim();
        if (!raw) return [];
        return raw.split(/\s+/).filter(c => /^\d+\|\d+$/.test(c)).map(c => {
            const [x, y] = c.split('|').map(Number);
            return { str: c, x, y };
        });
    }

    function getSelectedTroops() {
        const result = {};
        TROOPS.forEach(t => {
            const chk = document.getElementById(`kp-chk-${t.key}`);
            const amt = document.getElementById(`kp-amt-${t.key}`);
            if (chk && chk.checked) result[t.key] = parseInt(amt.value) || 0;
        });
        return result;
    }

    // ─── Berechnen ────────────────────────────────────────────────────────────
    function calculate() {
        const error = document.getElementById('kp-error');
        error.textContent = '';

        const coords = parseCoords();
        if (!coords.length) { error.textContent = 'Keine gΓΌltigen Koordinaten eingegeben.'; return; }

        const troops = getSelectedTroops();
        if (!Object.keys(troops).length) { error.textContent = 'Keine Truppen ausgewΓ€hlt.'; return; }

        const maxAttacksPerVillage = parseInt(document.getElementById('kp-max-attacks').value) || Infinity;
        const maxDist = parseInt(document.getElementById('kp-max-dist').value) || Infinity;

        const villages = readTroops();
        if (!villages.length) { error.textContent = 'Keine DΓΆrfer gefunden.'; return; }

        // Berechne Slots pro Dorf und expandiere in einzelne Slot-EintrΓ€ge
        const villageSlots = [];
        villages.forEach(v => {
            if (v.x === 0 && v.y === 0) return;
            const slotsByTroops = Math.min(...Object.entries(troops).map(([k, amt]) =>
                amt > 0 ? Math.floor((v.troops[k] || 0) / amt) : Infinity
            ));
            const slots = Math.min(slotsByTroops, maxAttacksPerVillage);
            for (let i = 0; i < slots; i++) villageSlots.push({ ...v });
        });

        if (!villageSlots.length) { error.textContent = 'Kein Dorf hat genug Truppen.'; return; }

        // Baue alle gΓΌltigen Paare (slot, coord) mit Distanz
        const pairs = [];
        villageSlots.forEach((v, vi) => {
            coords.forEach((c, ci) => {
                const dist = Math.sqrt((v.x - c.x) ** 2 + (v.y - c.y) ** 2);
                if (dist <= maxDist) pairs.push({ vi, ci, dist });
            });
        });

        // Sortiere nach Distanz aufsteigend
        pairs.sort((a, b) => a.dist - b.dist);

        // Greedy Zuweisung: jeder Slot und jede Koordinate nur einmal
        const usedSlots = new Set();
        const usedCoords = new Set();
        const assigned = [];

        pairs.forEach(({ vi, ci, dist }) => {
            if (usedSlots.has(vi) || usedCoords.has(ci)) return;
            usedSlots.add(vi);
            usedCoords.add(ci);
            assigned.push({ vi, ci, dist });
        });

        // 2-opt Verbesserung: tausche wenn Gesamtdistanz sinkt
        let improved = true;
        while (improved) {
            improved = false;
            for (let a = 0; a < assigned.length; a++) {
                for (let b = a + 1; b < assigned.length; b++) {
                    const ra = assigned[a];
                    const rb = assigned[b];
                    const va = villageSlots[ra.vi];
                    const vb = villageSlots[rb.vi];
                    const ca = coords[ra.ci];
                    const cb = coords[rb.ci];
                    const distAB = Math.sqrt((va.x - cb.x) ** 2 + (va.y - cb.y) ** 2);
                    const distBA = Math.sqrt((vb.x - ca.x) ** 2 + (vb.y - ca.y) ** 2);
                    if (distAB <= maxDist && distBA <= maxDist &&
                        distAB + distBA < ra.dist + rb.dist - 0.001) {
                        assigned[a] = { vi: ra.vi, ci: rb.ci, dist: distAB };
                        assigned[b] = { vi: rb.vi, ci: ra.ci, dist: distBA };
                        improved = true;
                    }
                }
            }
        }

        const attacks = assigned.map(({ vi, ci, dist }) => ({
            village: villageSlots[vi],
            target: coords[ci],
            dist: dist.toFixed(1)
        }));

        attacks.sort((a, b) => a.village.id.localeCompare(b.village.id) || a.dist - b.dist);
        renderResults(attacks, troops);
    }

    function renderResults(attacks, troops) {
        const results = document.getElementById('kp-results');
        if (!attacks.length) {
            results.innerHTML = '<div class="kp-error" style="padding:8px">Keine Angriffe berechnet.</div>';
            return;
        }
        const troopStr = Object.entries(troops).map(([k, v]) =>
            `${TROOPS.find(t => t.key === k).label}: ${v}`).join(' Β· ');

        results.innerHTML = `
            <div style="margin-bottom:8px;">
                <button id="kp-send-all" style="background:#5a7a00;color:#f0d090;border:1px solid #8aaa20;padding:6px 18px;font-size:14px;font-weight:bold;cursor:pointer;border-radius:3px;">
                    βš” Alle ${attacks.length} Angriffe senden
                </button>
                <span id="kp-send-status" style="margin-left:12px;color:#c8b464;font-size:13px;"></span>
            </div>
            <h3>βš” ${attacks.length} Angriffe β€” ${troopStr}</h3>
            ${attacks.map((a, i) => `
                <div class="kp-attack-row" id="kp-row-${i}">
                    <span><b>${i + 1}.</b> ${a.village.name} β†’ ${a.target.str} <small>(${a.dist} Felder)</small></span>
                    <button class="kp-attack-btn" data-idx="${i}">Angriff senden βš”</button>
                </div>
            `).join('')}
        `;

        // Einzelne Buttons
        results.querySelectorAll('.kp-attack-btn').forEach(btn => {
            btn.addEventListener('click', () => {
                const idx = parseInt(btn.dataset.idx);
                const a = attacks[idx];
                btn.disabled = true;
                btn.textContent = '⏳ Sende…';
                sendAttack(a.village.id, a.target, troops)
                    .then(() => {
                        btn.textContent = 'βœ“ Gesendet';
                        btn.style.background = '#1a4a00';
                        setTimeout(() => document.getElementById(`kp-row-${idx}`)?.remove(), 800);
                    })
                    .catch(err => {
                        btn.disabled = false;
                        btn.textContent = 'βœ— Fehler – erneut?';
                        btn.style.background = '#8b0000';
                        btn.title = String(err);
                    });
            });
        });

        // Alle senden Button
        document.getElementById('kp-send-all').addEventListener('click', async function() {
            this.disabled = true;
            this.textContent = '⏳ Wird gesendet…';
            const status = document.getElementById('kp-send-status');
            let sent = 0, failed = 0;

            for (let i = 0; i < attacks.length; i++) {
                const row = document.getElementById(`kp-row-${i}`);
                if (!row) continue; // bereits gesendet/entfernt
                const btn = row.querySelector('.kp-attack-btn');
                btn.disabled = true;
                btn.textContent = '⏳ Sende…';
                status.textContent = `${i + 1} / ${attacks.length}…`;

                try {
                    await sendAttack(attacks[i].village.id, attacks[i].target, troops);
                    btn.textContent = 'βœ“ Gesendet';
                    btn.style.background = '#1a4a00';
                    sent++;
                    setTimeout(() => row.remove(), 800);
                } catch(err) {
                    btn.disabled = false;
                    btn.textContent = 'βœ— Fehler';
                    btn.style.background = '#8b0000';
                    btn.title = String(err);
                    failed++;
                }

                // ZufΓ€lliger Delay 500–700ms zwischen Angriffen
                if (i < attacks.length - 1) {
                    await new Promise(r => setTimeout(r, 500 + Math.random() * 200));
                }
            }

            this.textContent = `βœ“ Fertig (${sent} gesendet${failed ? ', ' + failed + ' Fehler' : ''})`;
            status.textContent = '';
        });
    }

    function getPlaceForm(villageId) {
        return new Promise((resolve, reject) => {
            const base = `${window.location.origin}/game.php`;
            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 doc = iframe.contentDocument || iframe.contentWindow.document;
                    let csrfField = null, csrfValue = null;
                    doc.querySelectorAll('input[type=hidden]').forEach(inp => {
                        if (/^[0-9a-f]{15,}$/.test(inp.name)) {
                            csrfField = inp.name;
                            csrfValue = inp.value;
                        }
                    });
                    document.body.removeChild(iframe);
                    if (csrfField) resolve({ csrfField, csrfValue });
                    else reject(new Error('CSRF-Formularfeld nicht im iframe gefunden'));
                } catch(e) {
                    document.body.removeChild(iframe);
                    reject(e);
                }
            };
            iframe.onerror = () => { document.body.removeChild(iframe); reject(new Error('iframe-Fehler')); };
            document.body.appendChild(iframe);
        });
    }

    async function sendAttack(villageId, target, troops) {
        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-Token nicht gefunden');

        // Schritt 1: Versammlungsplatz in verstecktem iframe laden β†’ Formular-Token lesen
        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('Formular-Token nicht im iframe gefunden. Hidden inputs: ' + [...iDoc.querySelectorAll('input[type=hidden]')].map(i=>i.name).join(',')));
                } catch(e) {
                    document.body.removeChild(iframe);
                    reject(e);
                }
            };
            iframe.onerror = () => { document.body.removeChild(iframe); reject(new Error('iframe Ladefehler')); };
            document.body.appendChild(iframe);
        });

        // Schritt 2: POST β†’ BestΓ€tigungsseite
        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: '',
        });
        step2params.set(tokenField, tokenValue);
        ['spear','sword','axe','archer','spy','light','marcher','heavy','ram','catapult','snob'].forEach(k => {
            step2params.set(k, troops[k] != null ? String(troops[k]) : '');
        });

        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: hidden inputs aus BestΓ€tigungsseite sammeln + abschicken
        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('BestΓ€tigungs-Token (ch) nicht gefunden (Schritt 2)');

        // Fehlende Pflichtfelder ergΓ€nzen
        step3params.set('h', csrf);
        step3params.set('cb', 'troop_confirm_submit');
        step3params.set('submit_confirm', 'Angreifen');
        if (!step3params.has('attack_name')) step3params.set('attack_name', '');
        if (!step3params.has('building')) step3params.set('building', 'main');

        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} (Schritt 3)`);
    }

})();