Die Stämme Auto Scavenging

Automates scavenging in Die Stämme

// ==UserScript==
// @name         Die Stämme Auto Scavenging
// @namespace    http://tampermonkey.net/
// @version      1.3.2
// @description  Automates scavenging in Die Stämme
// @author       ricardofauch
// @match        https://*.die-staemme.de/game.php?*screen=place&mode=scavenge*
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

function injectStyles() {
        const styles = `
            #scavengeUI {
                position: fixed;
                left: 8%;
                top: 50%;
                transform: translateY(-50%);
                background-color: rgba(245, 245, 245, 0.95);
                border: 1px solid #967444;
                border-radius: 4px;
                padding: 8px;
                z-index: 9999;
                width: 140px;
                height: 140px;
                box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
            }
            #scavengeUI .title {
                font-weight: bold;
                color: #784B25;
                font-size: 12px;
                margin-bottom: 20px;
                text-align: center;
                font-family: Arial, sans-serif;
            }
            #scavengeUI label {
                display: block;
                color: #5C3C1D;
                font-size: 11px;
                font-weight: bold;
                margin-bottom: 4px;
                font-family: Arial, sans-serif;
                text-align: center;
            }
            #scavengeUI input[type="number"] {
                width: 100%;
                padding: 4px;
                border: 1px solid #967444;
                background-color: white;
                color: #4A3011;
                border-radius: 2px;
                font-size: 12px;
                font-weight: bold;
                text-align: center;
                outline: none;
                box-sizing: border-box;
                margin-bottom: 10px;
            }
            #scavengeUI .checkbox-container {
                display: flex;
                align-items: center;
                justify-content: center;
                margin: 8px 0;
                padding: 4px;
                background-color: rgba(255, 255, 255, 0.5);
                border-radius: 2px;
            }
            #scavengeUI .checkbox-container input[type="checkbox"] {
                margin-right: 5px;
            }
            #scavengeUI input:focus {
                border-color: #784B25;
                box-shadow: 0 0 2px #967444;
            }
            #scavengeUI:hover {
                background-color: rgba(255, 255, 255, 0.98);
            }
            #debugOutput {
                margin-top: 10px;
                padding-top: 10px;
                font-family: Arial, sans-serif;
                font-size: 10px;
                color: #5C3C1D;
            }
            #nextReloadTime {
                margin-top: 8px;
                padding: 4px;
                background-color: rgba(255, 255, 255, 0.5);
                border-radius: 2px;
                text-align: center;
                font-size: 11px;
                font-weight: bold;
                color: #5C3C1D;
            }
            .debug-entry {
                margin-bottom: 4px;
                padding: 2px;
                border-radius: 2px;
            }
            .debug-entry.info { color: #0066cc; }
            .debug-entry.warning { color: #cc6600; }
            .debug-entry.error { color: #cc0000; }
            .debug-entry.success { color: #006600; }
        `;
        const styleElement = document.createElement('style');
        styleElement.textContent = styles;
        document.head.appendChild(styleElement);
    }

    // Update Configuration
    const CONFIG = {
        TOTAL_SPEARS: 100,           // Default total spears
        MAX_SPEAR_PERCENTAGE: 0.35,  // Maximum percentage of total spears per run (35%)
        INCLUDE_SWORDS: true,       // Default sword setting
        MIN_SPEARS_THRESHOLD: 20,    // Minimum spears needed to start scavenging
        UI_LOAD_DELAY: 6000,         // Delay to wait for UI to load (in milliseconds)
        INPUT_PROCESS_DELAY: 1000,   // Delay after setting input before clicking (in milliseconds)
        INPUT_RETRY_DELAY: 200,      // Delay between input retry attempts (in milliseconds)
        MAX_INPUT_RETRIES: 5,        // Maximum number of times to retry setting the input
        MIN_RELOAD_TIME: 8,          // Minimum minutes before reload
        MAX_RELOAD_TIME: 12,         // Maximum minutes before reload
        DEBUG: false                  // Enable/disable debug logging
    };

    function createUI() {
        const ui = document.createElement('div');
        ui.id = 'scavengeUI';
        ui.innerHTML = `
            <div class="title">Das Meisterräuber Script</div>
            <label for="totalSpears">Speere Gesamt</label>
            <input type="number" id="totalSpears" min="0" step="1">
            <div class="checkbox-container">
                <input type="checkbox" id="includeSwords" ${CONFIG.INCLUDE_SWORDS ? 'checked' : ''}>
                <label for="includeSwords">Schwerter benutzen</label>
            </div>
            <div id="nextReloadTime">Nächster Reload: --:--</div>
            <div id="debugOutput"></div>
        `;
        document.body.appendChild(ui);

        // Get input elements
        const totalSpearsInput = document.getElementById('totalSpears');
        const includeSwordsCheckbox = document.getElementById('includeSwords');

        // Load saved values
        const savedSpears = localStorage.getItem('scavengeTotalSpears');
        const savedSwordsSetting = localStorage.getItem('scavengeIncludeSwords');

        if (savedSpears) {
            CONFIG.TOTAL_SPEARS = parseInt(savedSpears);
            totalSpearsInput.value = CONFIG.TOTAL_SPEARS;
        } else {
            totalSpearsInput.value = CONFIG.TOTAL_SPEARS;
        }

        if (savedSwordsSetting !== null) {
            CONFIG.INCLUDE_SWORDS = savedSwordsSetting === 'true';
            includeSwordsCheckbox.checked = CONFIG.INCLUDE_SWORDS;
        }

        // Add event listeners
        totalSpearsInput.addEventListener('change', (e) => {
            const newValue = parseInt(e.target.value) || CONFIG.TOTAL_SPEARS;
            CONFIG.TOTAL_SPEARS = newValue;
            localStorage.setItem('scavengeTotalSpears', newValue);
            debugLog(`Total spears updated to: ${newValue}`, 'info');
        });

        includeSwordsCheckbox.addEventListener('change', (e) => {
            CONFIG.INCLUDE_SWORDS = e.target.checked;
            localStorage.setItem('scavengeIncludeSwords', CONFIG.INCLUDE_SWORDS);
            debugLog(`Include swords setting updated to: ${CONFIG.INCLUDE_SWORDS}`, 'info');
        });
    }

    // Debug logging function
    function debugLog(message, type = 'info') {
        if (!CONFIG.DEBUG) return;

        const styles = {
            info: 'color: #0099ff; font-weight: bold;',
            warning: 'color: #ffa500; font-weight: bold;',
            error: 'color: #ff0000; font-weight: bold;',
            success: 'color: #00ff00; font-weight: bold;'
        };

        const timestamp = new Date().toLocaleTimeString();
        console.log(`%c[${timestamp}] ${message}`, styles[type]);

        // Update UI debug output
        const debugOutput = document.getElementById('debugOutput');
        if (debugOutput) {
            // Create new debug entry
            const entry = document.createElement('div');
            entry.className = `debug-entry ${type}`;
            entry.textContent = `[${timestamp}] ${message}`;

            // Add new entry at the top
            debugOutput.insertBefore(entry, debugOutput.firstChild);

            // Keep only last 4 messages
            while (debugOutput.children.length > 4) {
                debugOutput.removeChild(debugOutput.lastChild);
            }
        }
    }

    // Helper function to get random reload time
    function getRandomReloadTime() {
        const reloadTime = (Math.random() * (CONFIG.MAX_RELOAD_TIME - CONFIG.MIN_RELOAD_TIME) + CONFIG.MIN_RELOAD_TIME) * 60 * 1000;
        debugLog(`Generated random reload time: ${Math.round(reloadTime/1000/60)} minutes`);
        return reloadTime;
    }

    // Helper function to extract number from text
    function extractNumber(text) {
        const match = text.match(/\((\d+)\)/);
        const number = match ? parseInt(match[1]) : 0;
        debugLog(`Extracted number ${number} from text: ${text}`);
        return number;
    }

    // Helper function to set input value and verify it stuck
    async function setInputValueWithVerification(input, value, retryCount = 0) {
        debugLog(`Attempt ${retryCount + 1} to set ${input.name} input value to ${value}`, 'info');

        // Set the value and dispatch events
        input.value = value;
        input.dispatchEvent(new Event('input', { bubbles: true }));
        input.dispatchEvent(new Event('change', { bubbles: true }));

        // Wait a bit
        await new Promise(resolve => setTimeout(resolve, CONFIG.INPUT_RETRY_DELAY));

        // Check if value stuck
        if (input.value !== value.toString()) {
            debugLog(`${input.name} input verification failed! Current value: ${input.value}`, 'warning');

            if (retryCount < CONFIG.MAX_INPUT_RETRIES) {
                debugLog(`Retrying... (${retryCount + 1}/${CONFIG.MAX_INPUT_RETRIES})`, 'info');
                return setInputValueWithVerification(input, value, retryCount + 1);
            } else {
                debugLog('Max retries reached!', 'error');
                return false;
            }
        }

        debugLog(`${input.name} input value verified successfully`, 'success');
        return true;
    }


    // Main scavenging function
    async function handleScavenging() {
        debugLog('=== Starting scavenging operation ===', 'info');
        debugLog(`Current configuration:
        Total spears: ${CONFIG.TOTAL_SPEARS}
        Min spears threshold: ${CONFIG.MIN_SPEARS_THRESHOLD}
        UI load delay: ${CONFIG.UI_LOAD_DELAY}ms
        Reload time range: ${CONFIG.MIN_RELOAD_TIME}-${CONFIG.MAX_RELOAD_TIME} minutes`, 'info');

        // Wait for UI to load
        debugLog(`Waiting ${CONFIG.UI_LOAD_DELAY}ms for UI to load...`);
        await new Promise(resolve => setTimeout(resolve, CONFIG.UI_LOAD_DELAY));
        debugLog('UI load delay completed', 'success');

        // Get available spears
        const spearLink = document.querySelector('a.units-entry-all[data-unit="spear"]');
        if (!spearLink) {
            debugLog('Could not find spear count element!', 'error');
            scheduleReload();
            return;
        }

        const availableSpears = extractNumber(spearLink.textContent);
        debugLog(`Found ${availableSpears} available spears`, 'info');

        let availableSwords = 0;
        if (CONFIG.INCLUDE_SWORDS) {
            const swordLink = document.querySelector('a.units-entry-all[data-unit="sword"]');
            if (swordLink) {
                availableSwords = extractNumber(swordLink.textContent);
                debugLog(`Found ${availableSwords} available swords`, 'info');
            }
        }

        // Check if we have enough units
        if (availableSpears < CONFIG.MIN_SPEARS_THRESHOLD && (!CONFIG.INCLUDE_SWORDS || availableSwords === 0)) {
            debugLog(`Not enough units available, waiting for next reload`, 'warning');
            scheduleReload();
            return;
        }

        // Get the Start buttons again after setting the input
        const startButtons = Array.from(document.querySelectorAll('a.btn.free_send_button'));
        debugLog(`Found ${startButtons.length} start buttons after setting input`, 'info');

        if (startButtons.length === 0) {
            debugLog('No available start buttons found after setting input!', 'error');
            scheduleReload();
            return;
        }

        // Calculate units per option
        const evenDistributionMaxSpears = Math.floor(CONFIG.TOTAL_SPEARS / startButtons.length);
        const percentageMaxSpears = Math.floor(CONFIG.TOTAL_SPEARS * CONFIG.MAX_SPEAR_PERCENTAGE);
        const maxSpearsPerRun = Math.min(evenDistributionMaxSpears, percentageMaxSpears);
        const spearsToSend = Math.min(availableSpears, maxSpearsPerRun);

        let swordsToSend = 0;
        if (CONFIG.INCLUDE_SWORDS && availableSwords > 0) {
            swordsToSend = Math.floor(availableSwords / startButtons.length);
        }

        debugLog(`Will send ${spearsToSend} spears and ${swordsToSend} swords per run`, 'info');

        // Find and set inputs
        const spearInput = document.querySelector('input[name="spear"].unitsInput.input-nicer');
        const swordInput = document.querySelector('input[name="sword"].unitsInput.input-nicer');

        if (!spearInput) {
            debugLog('Could not find spear input!', 'error');
            scheduleReload();
            return;
        }

        // Set spear input
        const spearInputSuccess = await setInputValueWithVerification(spearInput, spearsToSend);

        // Set sword input if enabled
        let swordInputSuccess = true;
        if (CONFIG.INCLUDE_SWORDS && swordsToSend > 0 && swordInput) {
            swordInputSuccess = await setInputValueWithVerification(swordInput, swordsToSend);
        }

        if (!spearInputSuccess || !swordInputSuccess) {
            debugLog('Failed to set input values after all retries, scheduling reload', 'error');
            scheduleReload();
            return;
        }

        // Wait for input processing
        debugLog(`Waiting ${CONFIG.INPUT_PROCESS_DELAY}ms for input processing...`);
        await new Promise(resolve => setTimeout(resolve, CONFIG.INPUT_PROCESS_DELAY));

        // Verify one last time before clicking
        if (spearInput.value !== spearsToSend.toString()) {
            debugLog('Input value changed before clicking! Scheduling reload', 'error');
            scheduleReload();
            return;
        }


        // Get the last available button
        const lastButton = startButtons[startButtons.length - 1];

        // Click the button
        debugLog('Clicking start button...', 'info');
        lastButton.click();
        debugLog('Start button clicked', 'success');

        // Reload the page after clicking
        debugLog('Scheduling page reload in 1 second...', 'info');
        setTimeout(() => {
            debugLog('Reloading page...', 'info');
            window.location.reload();
        }, 1000);
    }

    // Schedule the next reload
    function scheduleReload() {
        const reloadTime = getRandomReloadTime();
        const nextReloadTime = new Date(Date.now() + reloadTime);
        const timeString = nextReloadTime.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });

        const nextReloadElement = document.getElementById('nextReloadTime');
        if (nextReloadElement) {
            nextReloadElement.textContent = `Nächster Reload: ${timeString} Uhr`;
        }

    debugLog(`Scheduling next reload at ${timeString}`, 'info');
    setTimeout(() => {
        debugLog('Executing scheduled reload...', 'info');
        window.location.reload();
    }, reloadTime);
}

    // Start the script
    debugLog('=== Script initialized ===', 'info');
    // Initialize the UI
    injectStyles();
    createUI();
    handleScavenging();

})();