Carbon's Mass Attack Planner
This Script can be ran initially two separate ways
1. Fetching Coordinates
Run script on Rankings page to fetch a specific players village coordinates, once this
is done then you will run the script a second time on your own combined overview page
you can have a certain group of village selected if you wish.
Enter World Speed and Unit Speed.
Check the box "Select All Troops on Attack" if you wish to use all troops in your villages.
now you can either go enemy village by village and enter
This Script can be ran initially two separate ways
1. Fetching Coordinates
Run script on Rankings page to fetch a specific players village coordinates, once this
is done then you will run the script a second time on your own combined overview page
you can have a certain group of village selected if you wish.
Enter World Speed and Unit Speed.
Check the box "Select All Troops on Attack" if you wish to use all troops in your villages.
now you can either go enemy village by village and enter
how many attacks for each in their respective dropdown boxes.
or
you can pick 1-9 in the "Attacks per village Master" and click "Set All" button.
Click Generate Plan and bam!
2. Manual Coordinate Input
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
click Save Coordinates and it will ask you to run the script again
Run the script again on your Combined Overview page.
or
you can pick 1-9 in the "Attacks per village Master" and click "Set All" button.
Click Generate Plan and bam!
2. Manual Coordinate Input
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
click Save Coordinates and it will ask you to run the script again
Run the script again on your Combined Overview page.
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.');
}
})();