how many attacks for each in their respective dropdown boxes.
you can pick 1-9 in the "Attacks per village Master" and click "Set All" button.
Run the script on your combined Overview page making sure if you want a certain group of villages used that is selected.
Click the Reset Stored Coordinates button.
you will get a Prompt that Coordinates are Cleared and be allowed to enter a list of Coordinates manually
Code:
javascript:(function () {
let storedCoordinates = JSON.parse(localStorage.getItem('fetchedCoordinates')) || [];
if (window.location.href.endsWith('=ranking')) {
const urlParams = new URLSearchParams(window.location.search);
const village = urlParams.get('village');
async function fetchData(url) {
const response = await fetch(url);
const text = await response.text();
const parser = new DOMParser();
return parser.parseFromString(text, 'text/html');
}
async function collectPlayerData() {
let currentPage = 1;
let players = {};
while (true) {
const url = `${window.location.origin}/game.php?village=${village}&screen=ranking&page=${currentPage}`;
const doc = await fetchData(url);
const playerLinks = doc.querySelectorAll('a[href*="screen=info_player&id="]');
playerLinks.forEach(link => {
const playerId = new URL(link.href).searchParams.get('id');
players[link.textContent.trim()] = playerId;
});
const nextPageLink = doc.querySelector(`a[href*="screen=ranking&page=${currentPage + 1}"]`);
if (!nextPageLink) break;
currentPage++;
}
return players;
}
function showPlayerSelectionPopup(players) {
const content = document.createElement('div');
const title = document.createElement('h3');
title.textContent = "Carbon's Coordinate Grabber";
title.style = 'color:black;font-weight:bold;text-align:center;margin-bottom:10px;';
content.appendChild(title);
const dropdown = document.createElement('select');
dropdown.style = 'width: 100%; padding: 5px; margin-bottom: 10px;';
Object.keys(players).forEach(playerName => {
const option = document.createElement('option');
option.value = players[playerName];
option.textContent = playerName;
dropdown.appendChild(option);
});
const grabberButton = document.createElement('button');
grabberButton.textContent = 'Grab Coordinates';
grabberButton.style = 'width: 100%; padding: 10px; border: 1px solid #5f3e11; background: #9a7c4b; color: white; font-weight: bold; cursor: pointer;';
grabberButton.onclick = async () => {
const selectedPlayerId = dropdown.value;
storedCoordinates = await fetchCoordinates(selectedPlayerId);
localStorage.setItem('fetchedCoordinates', JSON.stringify(storedCoordinates));
alert('Coordinates are saved. Please run the script again from Combined Overview.');
document.body.removeChild(popup);
};
content.appendChild(dropdown);
content.appendChild(grabberButton);
const popup = createPopup('Carbon\'s Coordinate Grabber', content, '500px', '350px');
}
async function fetchCoordinates(playerId) {
let currentPage = 1;
let coordinates = [];
while (true) {
const url = `${window.location.origin}/game.php?village=${village}&screen=info_player&id=${playerId}&page=${currentPage}`;
const doc = await fetchData(url);
const coordLinks = doc.querySelectorAll('a[href*="screen=info_village&id="]');
coordLinks.forEach(link => {
const coordText = link.textContent.match(/\((\d+\|\d+)\)/);
if (coordText) coordinates.push(coordText[1]);
});
const nextPageLink = doc.querySelector(`a[href*="screen=info_player&id=${playerId}&page=${currentPage + 1}"]`);
if (!nextPageLink) break;
currentPage++;
}
return coordinates;
}
function createPopup(title, content, width = '400px', height = '400px') {
const popup = document.createElement('div');
popup.style.position = 'fixed';
popup.style.top = '50%';
popup.style.left = '50%';
popup.style.transform = 'translate(-50%, -50%)';
popup.style.background = '#f4e2d0';
popup.style.border = '2px solid #b38b6d';
popup.style.padding = '15px';
popup.style.borderRadius = '8px';
popup.style.zIndex = '10000';
popup.style.width = width;
popup.style.height = height;
popup.style.boxSizing = 'border-box';
document.body.appendChild(popup);
popup.appendChild(content);
return popup;
}
async function main() {
const players = await collectPlayerData();
if (Object.keys(players).length === 0) {
alert('No players found.');
return;
}
showPlayerSelectionPopup(players);
}
main();
} else if (window.location.href.includes('=combined')) {
if (!storedCoordinates.length) {
showManualCoordinateInputPopup();
return;
}
function extractWorldSpeed() {
const scriptTags = document.querySelectorAll('script');
for (let script of scriptTags) {
if (script.innerText.includes('InfernalWars._settings')) {
const settingsMatch = script.innerText.match(/"speed":\s*(\d+)/);
if (settingsMatch) {
return parseFloat(settingsMatch[1]);
}
}
}
return 1;
}
function executeMassAttackPlanner() {
function calculateDistance(coord1, coord2) {
const [x1, y1] = coord1.split('|').map(Number);
const [x2, y2] = coord2.split('|').map(Number);
if (!isNaN(x1) && !isNaN(y1) && !isNaN(x2) && !isNaN(y2)) {
const distance = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
return Math.round(distance);
} else {
return false;
}
}
function calculateDynamicTravelTime(unit, distance, worldSpeed, unitSpeed) {
const baseTravelTimes = {
ram: 1697,
snob: 2546
};
const baseTime = baseTravelTimes[unit];
return (baseTime / worldSpeed) * (1 / unitSpeed) * distance;
}
function fetchAdditionalPages(villageData = [], currentPage = 1) {
const villageTable = document.getElementById('combined_table');
if (!villageTable) {
alert('Village data table not found on this page.');
return;
}
const rows = villageTable.querySelectorAll('tr');
rows.forEach(row => {
const link = row.cells[1]?.querySelector('a');
if (link) {
const villageId = link.href.match(/village=(\d+)/)[1];
const coordinates = link.textContent.match(/\((\d+\|\d+)\)/)[1];
villageData.push({ villageId, coordinates });
}
});
const nextPageLink = document.querySelector(`a[href*="page=${currentPage + 1}"]:not([href^="https://forum.infernal-wars.com"])`);
if (nextPageLink) {
fetch(nextPageLink.href)
.then(response => response.text())
.then(html => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
document.body.innerHTML = doc.body.innerHTML;
fetchAdditionalPages(villageData, currentPage + 1);
})
.catch(error => console.error('Error fetching additional pages:', error));
} else {
if (villageData.length > 0) {
showCoordinateInputPopup(villageData);
} else {
alert("No villages found.");
}
}
}
function showCoordinateInputPopup(villageData) {
const popup = document.createElement('div');
popup.style = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:390px;height:450px;border:1px solid #c1a264;background:#f4e4bc;padding:10px;z-index:10000;box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.5);box-sizing: border-box;overflow-y: auto;';
const title = document.createElement('h3');
title.textContent = "Carbon's Mass Attack Planner";
title.style = 'color:black;font-weight:bold;text-align:center;margin-bottom:8px;font-size:12px;';
popup.appendChild(title);
const worldSpeedLabel = document.createElement('label');
worldSpeedLabel.textContent = 'World Speed:';
worldSpeedLabel.style = 'display:block;margin-bottom:4px;color:#5f3e11;font-weight:bold;font-size:12px;';
popup.appendChild(worldSpeedLabel);
const worldSpeedInput = document.createElement('input');
worldSpeedInput.type = 'number';
worldSpeedInput.value = extractWorldSpeed();
worldSpeedInput.style = 'width:100%;margin-bottom:8px;border:1px solid #c1a264;background:#fffbe5;color:#5f3e11;padding:3px;font-size:12px;';
popup.appendChild(worldSpeedInput);
const unitSpeedLabel = document.createElement('label');
unitSpeedLabel.textContent = 'Unit Speed:';
unitSpeedLabel.style = 'display:block;margin-bottom:4px;color:#5f3e11;font-weight:bold;font-size:12px;';
popup.appendChild(unitSpeedLabel);
const unitSpeedInput = document.createElement('input');
unitSpeedInput.type = 'number';
unitSpeedInput.value = 1;
unitSpeedInput.style = 'width:100%;margin-bottom:8px;border:1px solid #c1a264;background:#fffbe5;color:#5f3e11;padding:3px;font-size:12px;';
popup.appendChild(unitSpeedInput);
const selectAllCheckboxContainer = document.createElement('div');
selectAllCheckboxContainer.style = 'display:flex;align-items:center;margin-bottom:8px;';
const selectAllCheckbox = document.createElement('input');
selectAllCheckbox.type = 'checkbox';
selectAllCheckbox.id = 'selectAllTroopsCheckbox';
selectAllCheckbox.style = 'margin-right:5px;';
selectAllCheckbox.checked = true;
const selectAllLabel = document.createElement('label');
selectAllLabel.htmlFor = 'selectAllTroopsCheckbox';
selectAllLabel.textContent = 'Select All Troops on Attack';
selectAllLabel.style = 'color:#5f3e11;font-weight:bold;font-size:12px;';
selectAllCheckboxContainer.appendChild(selectAllCheckbox);
selectAllCheckboxContainer.appendChild(selectAllLabel);
popup.appendChild(selectAllCheckboxContainer);
const masterContainer = document.createElement('div');
masterContainer.style = 'display: flex; align-items: center; gap: 5px; margin-bottom: 8px;';
const masterLabel = document.createElement('label');
masterLabel.textContent = 'Attacks per village Master:';
masterLabel.style = 'color:#5f3e11;font-weight:bold;font-size:12px;';
masterContainer.appendChild(masterLabel);
const masterDropdown = document.createElement('select');
masterDropdown.style = 'width: 50px; margin-right: 5px; font-size:12px;';
for (let i = 1; i <= 10; i++) {
const option = document.createElement('option');
option.value = i;
option.textContent = i;
masterDropdown.appendChild(option);
}
masterContainer.appendChild(masterDropdown);
const setAllButton = document.createElement('button');
setAllButton.textContent = 'Set All';
setAllButton.style = 'padding: 4px 8px; border: 1px solid #5f3e11; background: #9a7c4b; color: white; font-weight: bold; cursor: pointer; font-size:12px;';
setAllButton.onclick = () => {
const value = masterDropdown.value;
dropdowns.forEach(dropdown => dropdown.value = value);
};
masterContainer.appendChild(setAllButton);
const resetButton = document.createElement('button');
resetButton.textContent = 'Reset Stored Coordinates';
resetButton.style = 'padding: 4px 8px; border: 1px solid #5f3e11; background: #9a7c4b; color: white; font-weight: bold; cursor: pointer; font-size:12px;';
resetButton.onclick = () => {
localStorage.removeItem('fetchedCoordinates');
storedCoordinates = [];
alert('Stored coordinates cleared. You can now enter new coordinates manually.');
document.body.removeChild(popup);
showManualCoordinateInputPopup();
};
masterContainer.appendChild(resetButton);
popup.appendChild(masterContainer);
const enemyContainer = document.createElement('div');
enemyContainer.style = 'display: grid; grid-template-columns: repeat(4, 1fr); gap: 5px;';
const dropdowns = [];
storedCoordinates.forEach((coord, index) => {
const coordDiv = document.createElement('div');
coordDiv.style = 'display: flex; flex-direction: column; align-items: center; margin-bottom: 4px;';
const coordLabel = document.createElement('span');
coordLabel.textContent = coord;
coordLabel.style = 'margin-bottom: 2px; font-size: 10px; color: #5f3e11;';
coordDiv.appendChild(coordLabel);
const dropdown = document.createElement('select');
dropdown.style = 'width: 50px; font-size: 10px;';
for (let i = 1; i <= 10; i++) {
const option = document.createElement('option');
option.value = i;
option.textContent = i;
dropdown.appendChild(option);
}
coordDiv.appendChild(dropdown);
dropdowns.push(dropdown);
enemyContainer.appendChild(coordDiv);
});
popup.appendChild(enemyContainer);
const buttonContainer = document.createElement('div');
buttonContainer.style = 'display: flex; gap: 5px; justify-content: center; padding-top: 4px;';
const generatePlanButton = document.createElement('button');
generatePlanButton.textContent = 'Generate Plan';
generatePlanButton.style = 'flex: 1; padding:6px;border:1px solid #5f3e11;background:#9a7c4b;color:white;font-weight:bold;cursor:pointer;font-size:12px;';
generatePlanButton.onclick = () => {
const attackOrders = [];
storedCoordinates.forEach((coord, index) => {
const numAttacks = parseInt(dropdowns[index].value);
for (let i = 0; i < numAttacks; i++) {
attackOrders.push(coord);
}
});
const worldSpeed = parseFloat(worldSpeedInput.value);
const unitSpeed = parseFloat(unitSpeedInput.value);
const selectAllTroops = selectAllCheckbox.checked;
generatePlan(villageData, attackOrders, worldSpeed, unitSpeed, selectAllTroops);
document.body.removeChild(popup);
};
buttonContainer.appendChild(generatePlanButton);
const cancelButton = document.createElement('button');
cancelButton.textContent = 'Cancel';
cancelButton.style = 'flex: 1; padding:6px;border:1px solid #5f3e11;background:#9a7c4b;color:white;font-weight:bold;cursor:pointer;font-size:12px;';
cancelButton.onclick = () => {
document.body.removeChild(popup);
};
buttonContainer.appendChild(cancelButton);
popup.appendChild(buttonContainer);
document.body.appendChild(popup);
}
function generatePlan(villageData, attackOrders, worldSpeed, unitSpeed, selectAllTroops) {
const attackPlans = [];
const remainingUserVillages = [...villageData];
attackOrders.forEach((enemyVillage) => {
if (remainingUserVillages.length === 0) return;
let closestVillage = null;
let shortestDistance = Infinity;
remainingUserVillages.forEach((userVillage) => {
const distance = calculateDistance(userVillage.coordinates, enemyVillage);
if (distance !== false && distance < shortestDistance) {
closestVillage = userVillage;
shortestDistance = distance;
}
});
if (closestVillage) {
const travelTime = calculateDynamicTravelTime('ram', shortestDistance, worldSpeed, unitSpeed);
attackPlans.push({ userVillage: closestVillage, enemyVillage, distance: shortestDistance, travelTime });
remainingUserVillages.splice(remainingUserVillages.indexOf(closestVillage), 1);
}
});
attackPlans.sort((a, b) => b.travelTime - a.travelTime);
showPlanPopup(attackPlans, selectAllTroops);
}
function showPlanPopup(attackPlans, selectAllTroops) {
const popup = document.createElement('div');
popup.style = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:450px;height:375px;border:1px solid #c1a264;background:#f4e4bc;padding:10px;z-index:10000;box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.5);overflow:auto;box-sizing: border-box;display: flex; flex-direction: column; justify-content: space-between;';
const title = document.createElement('h3');
title.textContent = "Carbon's Mass Attack Planner";
title.style = 'color:black;font-weight:bold;text-align:center;margin-bottom:5px;font-size:12px;';
popup.appendChild(title);
const planTitle = document.createElement('h3');
planTitle.textContent = 'Attack Plan';
planTitle.style = 'color:#5f3e11;font-weight:bold;text-align:center;margin-bottom:5px;font-size:12px;';
popup.appendChild(planTitle);
const planContainer = document.createElement('div');
planContainer.style = 'flex-grow: 1; overflow-y: auto; margin-bottom: 4px; border: 1px solid #c1a264; background: #fffbe5; padding: 4px; font-size: 12px;';
popup.appendChild(planContainer);
attackPlans.forEach(({ userVillage, enemyVillage, distance, travelTime }) => {
const planLine = document.createElement('div');
planLine.style = 'margin-bottom:4px;color:#5f3e11;font-size:10px;display:flex;justify-content:space-between;align-items:center;';
const minutes = Math.floor(travelTime / 60);
const seconds = Math.floor(travelTime % 60);
const planText = `${userVillage.coordinates} --> ${enemyVillage} - ${minutes}m ${seconds}s`;
const planTextSpan = document.createElement('span');
planTextSpan.textContent = planText;
planLine.appendChild(planTextSpan);
const attackButton = document.createElement('button');
attackButton.textContent = 'Attack';
attackButton.style = 'padding:3px 6px;border:1px solid #5f3e11;background:#9a7c4b;color:white;font-weight:bold;cursor:pointer;font-size:10px;';
attackButton.onclick = () => {
const attackUrl = window.location.href.replace(/village=\d+/, `village=${userVillage.villageId}`).replace('screen=overview_villages&mode=combined', 'screen=place');
const attackWindow = window.open(attackUrl, '_blank');
attackWindow.onload = () => {
const scriptContent = `
(function() {
const observer = new MutationObserver((mutations, obs) => {
const targetInput = document.querySelector('input.target-input-field');
const selectAllLink = document.getElementById('selectAllUnits');
if (targetInput) {
targetInput.value = '${enemyVillage}';
if (${selectAllTroops} && selectAllLink) {
selectAllLink.click();
}
obs.disconnect();
}
});
observer.observe(document.body, { childList: true, subtree: true });
})();
`;
const scriptElement = attackWindow.document.createElement('script');
scriptElement.textContent = scriptContent;
attackWindow.document.head.appendChild(scriptElement);
planLine.style.textDecoration = 'line-through';
};
};
planLine.appendChild(attackButton);
planContainer.appendChild(planLine);
});
const closeButton = document.createElement('button');
closeButton.textContent = 'Close';
closeButton.style = 'width:100%;padding:6px;margin-top:4px;border:1px solid #5f3e11;background:#9a7c4b;color:white;font-weight:bold;cursor:pointer;font-size:12px;';
closeButton.onclick = () => {
document.body.removeChild(popup);
};
popup.appendChild(closeButton);
document.body.appendChild(popup);
}
fetchAdditionalPages();
}
function showManualCoordinateInputPopup() {
const popup = document.createElement('div');
popup.style = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:375px;height:275px;border:1px solid #c1a264;background:#f4e4bc;padding:10px;z-index:10000;box-sizing:border-box;';
const title = document.createElement('h3');
title.textContent = "Enter Enemy Coordinates Manually";
title.style = 'color:black;font-weight:bold;text-align:center;margin-bottom:8px;font-size:12px;';
popup.appendChild(title);
const infoText = document.createElement('p');
infoText.textContent = 'If you would like to fetch coordinates from a specific user, run the script on the Ranking page first.';
infoText.style = 'color:#5f3e11;text-align:center;margin-bottom:8px;font-size:10px;';
popup.appendChild(infoText);
const textarea = document.createElement('textarea');
textarea.placeholder = "Paste coordinates or any text containing (xxx|yyy) or xxx|yyy coordinates";
textarea.style = 'width:100%;height:80px;margin-bottom:8px;border:1px solid #c1a264;background:#fffbe5;color:#5f3e11;padding:4px;font-size:10px;';
popup.appendChild(textarea);
const buttonContainer = document.createElement('div');
buttonContainer.style = 'display:flex;justify-content:space-between;gap:5px;';
const saveButton = document.createElement('button');
saveButton.textContent = 'Save Coordinates';
saveButton.style = 'padding:4px 6px;border:1px solid #5f3e11;background:#9a7c4b;color:white;font-weight:bold;cursor:pointer;font-size:10px;';
saveButton.onclick = () => {
const coords = [];
const regex = /\(?(\d+\|\d+)\)?/g;
let match;
while ((match = regex.exec(textarea.value)) !== null) {
coords.push(match[1]);
}
if (coords.length > 0) {
storedCoordinates = coords;
localStorage.setItem('fetchedCoordinates', JSON.stringify(storedCoordinates));
alert('Coordinates saved. Please run the script again.');
document.body.removeChild(popup);
} else {
alert('No valid coordinates found. Please check your input.');
}
};
buttonContainer.appendChild(saveButton);
const closeButton = document.createElement('button');
closeButton.textContent = 'Close';
closeButton.style = 'padding:4px 6px;border:1px solid #5f3e11;background:#9a7c4b;color:white;font-weight:bold;cursor:pointer;font-size:10px;';
closeButton.onclick = () => {
document.body.removeChild(popup);
};
buttonContainer.appendChild(closeButton);
popup.appendChild(buttonContainer);
document.body.appendChild(popup);
}
executeMassAttackPlanner();
} else {
alert('Please run this script on either the =ranking or =combined page.');
}
})();