// ==UserScript== // @name Tribal Wars Auto Resource Builder with reduction // @namespace http://tampermonkey.net/ // @version 1.0 // @description Automatically builds resource buildings with level balancing // @author You // @match https://*.die-staemme.de/game.php?village=*&screen=main* // @grant none // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; // Configuration const CHECK_INTERVAL = 5 * 61 * 1000; // 5 minutes in milliseconds const BUILDING_PRIORITY = ['wood', 'stone', 'iron']; const LEVEL_DIFFERENCE_THRESHOLD = 3; // Maximum allowed level difference const WAIT_FOR_HIGHER_PRIORITY = 10 * 60; // Wait up to 10 minutes for higher priority building const DEBUG = true; function debugLog(message, data = null) { if (!DEBUG) return; const timestamp = new Date().toLocaleTimeString(); if (data) { console.log(`[${timestamp}] ${message}`, data); } else { console.log(`[${timestamp}] ${message}`); } } function getBuildingLevel(buildingName) { debugLog(`Getting level for ${buildingName}`); try { // Find the row containing the building const row = document.querySelector(`#main_buildrow_${buildingName}`); if (!row) { debugLog(`No row found for ${buildingName}`); return null; } // Find the build button const buildButton = row.querySelector(`a.btn-build[id*="_${buildingName}_"]`); if (!buildButton) { debugLog(`No build button found for ${buildingName}`); return null; } // Get the next level from data attribute and subtract 1 const nextLevel = parseInt(buildButton.getAttribute('data-level-next')); if (isNaN(nextLevel)) { debugLog(`Could not parse next level for ${buildingName}`); return null; } const currentLevel = nextLevel - 1; debugLog(`${buildingName} current level:`, currentLevel); return currentLevel; } catch (error) { debugLog(`Error getting level for ${buildingName}:`, error); return null; } } function getResourceLevels() { debugLog('Getting current resource levels...'); const levels = {}; let highestLevel = 0; for (const resource of BUILDING_PRIORITY) { levels[resource] = getBuildingLevel(resource); if (levels[resource] !== null) { highestLevel = Math.max(highestLevel, levels[resource]); } } debugLog('Resource levels summary:', { levels, highestLevel }); return { levels, highestLevel }; } function getRemainingBuildTime(buildingName) { try { const row = document.querySelector(`#main_buildrow_${buildingName}`); if (!row) { debugLog(`No row found for ${buildingName} when checking time`); return Infinity; } // Check if there's a running timer const timerCell = row.querySelector('td:nth-child(5)'); if (!timerCell) { debugLog(`No timer cell found for ${buildingName}`); return Infinity; } const timeText = timerCell.textContent.trim(); if (!timeText) return 0; const [hours, minutes, seconds] = timeText.split(':').map(Number); const totalSeconds = hours * 3600 + minutes * 60 + seconds; debugLog(`${buildingName} remaining build time: ${timeText} (${totalSeconds} seconds)`); return totalSeconds; } catch (error) { debugLog(`Error getting remaining time for ${buildingName}:`, error); return Infinity; } } function canBuildResource(buildingName) { try { const row = document.querySelector(`#main_buildrow_${buildingName}`); if (!row) { debugLog(`No row found for ${buildingName} when checking buildability`); return false; } // Find the -20% button specifically const buildButton = row.querySelector(`#main_buildlink_${buildingName}_cheap`); if (!buildButton) { debugLog(`No cheap build button found for ${buildingName}`); return false; } // Check if button has the disabled class const isDisabled = buildButton.classList.contains('btn-bcr-disabled'); // Check if there's a valid build link const hasValidHref = buildButton.getAttribute('href') && buildButton.getAttribute('href').includes('cheap') && buildButton.getAttribute('href') !== '#'; // Check if the button is inside a cell with class 'build_options' const inBuildOptions = buildButton.closest('.build_options') !== null; // A button is buildable if it has a valid href, is in the build options cell, and is not disabled const canBuild = hasValidHref && inBuildOptions && !isDisabled; debugLog(`Checking if ${buildingName} can be built with -20%:`, { buttonFound: true, hasValidHref: hasValidHref, inBuildOptions: inBuildOptions, isDisabled: isDisabled, canBuild: canBuild, href: buildButton.getAttribute('href'), buttonText: buildButton.textContent.trim(), buttonClasses: buildButton.className, parentCell: buildButton.closest('td')?.className || 'no parent cell' }); return canBuild; } catch (error) { debugLog(`Error checking if ${buildingName} can be built:`, error); return false; } } function willBeAvailableSoon(buildingName) { const remainingTime = getRemainingBuildTime(buildingName); const willBeSoon = remainingTime > 0 && remainingTime <= WAIT_FOR_HIGHER_PRIORITY; debugLog(`Checking if ${buildingName} will be available soon:`, { remainingTime, threshold: WAIT_FOR_HIGHER_PRIORITY, willBeSoon }); return willBeSoon; } function applyBuildTimeReduction() { try { // Find all build time reduction buttons const reductionButtons = document.querySelectorAll('a.order_feature.btn.btn-btr'); if (!reductionButtons || reductionButtons.length === 0) { debugLog('No build time reduction buttons found'); return false; } // Get the last button (most recently added building) const lastButton = reductionButtons[reductionButtons.length - 1]; // Click the button lastButton.click(); debugLog('Clicked build time reduction button'); return true; } catch (error) { debugLog('Error applying build time reduction:', error); return false; } } function buildResource(buildingName) { debugLog(`Attempting to build ${buildingName} with -20% discount`); try { const row = document.querySelector(`#main_buildrow_${buildingName}`); if (!row) { debugLog(`No row found for ${buildingName} when trying to build`); return false; } const buildButton = row.querySelector(`#main_buildlink_${buildingName}_cheap`); if (!buildButton) { debugLog(`No cheap build button found for ${buildingName}`); return false; } const buildUrl = buildButton.getAttribute('href'); if (!buildUrl || !buildUrl.includes('cheap')) { debugLog(`No valid cheap build href found for ${buildingName}`); return false; } debugLog(`Clicking -20% build button for ${buildingName} with URL: ${buildUrl}`); window.location.href = buildUrl; // Wait a short moment for the page to update then apply reduction setTimeout(applyBuildTimeReduction, 1000); return true; } catch (error) { debugLog(`Error building ${buildingName}:`, error); return false; } } function isConstructionInProgress() { // Check for buildorder element const buildorder = document.querySelector('#buildorder_4'); const isBuilding = buildorder !== null; debugLog('Checking for ongoing construction:', { buildorderFound: isBuilding, elementId: isBuilding ? buildorder.id : 'not found' }); return isBuilding; } function reduceLongBuilds() { try { // Get all actual build rows (excluding headers and progress bars) const buildRows = document.querySelectorAll('#buildqueue tr.buildorder_wood, #buildqueue tr.buildorder_stone, #buildqueue tr.buildorder_iron, #buildqueue tr.buildorder_farm, #buildqueue tr.buildorder_market'); debugLog('Found build rows:', buildRows.length); for (const row of buildRows) { // Get the duration cell - looking specifically at the td with nowrap class const durationCell = row.querySelector('td.nowrap.lit-item'); if (!durationCell) { debugLog('No duration cell found for row:', row.className); continue; } // Get the span containing the time const timeSpan = durationCell.querySelector('span'); if (!timeSpan) { debugLog('No time span found in duration cell'); continue; } // Get the duration text const durationText = timeSpan.textContent.trim(); if (!durationText) { debugLog('Empty duration text'); continue; } // Parse the time const [hours, minutes, seconds] = durationText.split(':').map(Number); const totalHours = hours + minutes/60 + seconds/3600; // Get building info for logging const buildingCell = row.querySelector('td.lit-item'); const buildingName = buildingCell ? buildingCell.textContent.trim().split('\n')[0] : 'Unknown'; debugLog('Checking build duration:', { building: buildingName, duration: durationText, totalHours: totalHours, rowClass: row.className }); // If duration is over 2 hours if (totalHours > 2) { // Find the reduction button in this row const reductionButton = row.querySelector('a.order_feature.btn.btn-btr:not(.btn-instant)'); if (reductionButton) { debugLog('Found long build, clicking reduction button:', { building: buildingName, duration: durationText, buttonText: reductionButton.textContent.trim() }); reductionButton.click(); return true; } } } debugLog('No builds over 2 hours found needing reduction'); return false; } catch (error) { debugLog('Error in reduceLongBuilds:', error); return false; } } function checkAndBuild() { debugLog('Starting building check cycle...'); reduceLongBuilds(); try { // First check if there's ongoing construction if (isConstructionInProgress()) { debugLog('Construction already in progress, skipping build check'); return; } const { levels, highestLevel } = getResourceLevels(); let buildableResources = []; // Rest of the function remains the same... for (const resource of BUILDING_PRIORITY) { if (canBuildResource(resource)) { const resourceInfo = { name: resource, level: levels[resource], levelDifference: highestLevel - levels[resource], priority: BUILDING_PRIORITY.indexOf(resource) }; buildableResources.push(resourceInfo); debugLog(`${resource} is buildable:`, resourceInfo); } } if (buildableResources.length === 0) { debugLog('No resources can be built at this time'); return; } debugLog('Buildable resources before sorting:', buildableResources); // Sort resources by level difference and priority buildableResources.sort((a, b) => { const aIsBehind = a.levelDifference >= LEVEL_DIFFERENCE_THRESHOLD; const bIsBehind = b.levelDifference >= LEVEL_DIFFERENCE_THRESHOLD; if (aIsBehind && !bIsBehind) return -1; if (!aIsBehind && bIsBehind) return 1; return a.priority - b.priority; }); const selectedBuilding = buildableResources[0]; debugLog('Selected building for construction:', selectedBuilding); // Check if any higher priority building will be available soon const shouldWait = BUILDING_PRIORITY.some((resource, index) => { if (index < BUILDING_PRIORITY.indexOf(selectedBuilding.name)) { return willBeAvailableSoon(resource); } return false; }); if (shouldWait) { debugLog('Waiting for higher priority building to become available'); return; } // Build the selected resource debugLog('Proceeding with building construction:', selectedBuilding); if (buildResource(selectedBuilding.name)) { debugLog('Building command sent successfully'); } } catch (error) { debugLog('Error in checkAndBuild:', error); } } // Initial check debugLog('Script initialized, performing initial check...'); checkAndBuild(); // Set up periodic page reload debugLog(`Setting up periodic page reload every ${CHECK_INTERVAL/1000} seconds`); setInterval(() => { debugLog('Triggering page reload for next check'); window.location.reload(); }, CHECK_INTERVAL); debugLog('Script setup completed successfully'); })();