// ==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
// ==/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');
})();