// ==UserScript==
// @name Travian Kingdoms Tools
// @description Travian Kingdoms Tools: Search for valleys and oases
// @match https://*.kingdoms.com/*
// @version 1.0.1
// @namespace https://greasyfork.org/users/563852
// ==/UserScript==
(async () => {
'use strict';
let session = undefined;
let searching = false;
(function(request) {
XMLHttpRequest.prototype.send = function() {
const argument = arguments[0];
if (typeof argument === 'string' || argument instanceof String) {
if (argument.includes('session')) {
session = JSON.parse(argument).session;
}
}
request.apply(this, arguments);
};
})(XMLHttpRequest.prototype.send);
setInterval(async () => {
const node = document.querySelector('.travian-kingdoms-tools');
if (window.location.href.includes('page:map')) {
if (!node) {
const { text, node, nodeOne, nodeTwo } = createFooter();
node.addEventListener('click', async () => {
const timestamp = new Date();
if (searching) {
text.innerText = 'Search for valleys and oases';
nodeOne.style.display = 'none';
nodeTwo.style.display = 'none';
} else {
text.innerText = 'Searching will take a moment...';
const { valleys: mapValleys, oases: mapOases, names } = await processMap();
const { valleys: oasesValleys, oases } = await processOases(session, mapValleys, mapOases, names);
const valleys = await processValleys(oasesValleys, oases);
const { valleyTable, oasisTable } = createTables();
oases.map((oasis, index) => createRow(oasisTable, index + 1, createOasisContent(oasis)));
valleys.map((valley, index) => createRow(valleyTable, index + 1, createValleyContent(valley)));
text.innerText = `Searching was successfully completed in ${parseInt(((new Date() - timestamp) / 1000).toString())} seconds!`;
nodeOne.appendChild(valleyTable);
nodeTwo.appendChild(oasisTable);
nodeOne.style.display = 'block';
nodeTwo.style.display = 'block';
}
searching = !searching;
});
}
} else {
if (node) {
node.remove();
document.querySelector('.travian-kingdoms-tools-table-one').remove();
document.querySelector('.travian-kingdoms-tools-table-two').remove();
}
}
}, 1000);
})();
const processMap = async () => {
const mapVillageId = /villId:(\d+)/.exec(window.location.toString());
const { response: { privateApiKey } } = await (await fetch(`https://${window.location.hostname}/api/external.php?action=requestApiKey&email=email@example.com&siteName=Example&siteUrl=https://example.com&public=0`)).json();
const { response: { map: { cells }, players } } = await (await fetch(`https://${window.location.hostname}/api/external.php?action=getMapData&privateApiKey=${privateApiKey}`)).json();
const valleys = [];
const oases = [];
let villageX = 0;
let villageY = 0;
if (mapVillageId) {
players.map(({ villages }) => {
villages.map(({ villageId, x, y }) => {
if (villageX === 0 && villageY === 0 && villageId === mapVillageId[1]) {
villageX = parseInt(x);
villageY = parseInt(y);
}
});
});
}
const names = (await Promise.all(cells.map(async ({ id, x, y, resType, oasis }) => {
if (['10', '11', '20', '21', '30', '31', '40', '41'].includes(oasis)) {
oases.push({
externalId: parseInt(id),
type: parseInt(oasis),
x: parseInt(x),
y: parseInt(y),
distance: parseInt(Math.sqrt(Math.pow(x - villageX, 2) + Math.pow(y - villageY, 2)).toString()),
elephant: 0,
tiger: 0,
crocodile: 0,
bear: 0,
wolf: 0,
boar: 0,
bat: 0,
snake: 0,
spider: 0,
rat: 0,
});
return `MapDetails:${id}`;
}
if (['11115', '3339'].includes(resType)) {
valleys.push({
externalId: parseInt(id),
type: parseInt(resType),
x: parseInt(x),
y: parseInt(y),
bonus: 0,
distance: parseInt(Math.sqrt(Math.pow(x - villageX, 2) + Math.pow(y - villageY, 2)).toString()),
occupied: false,
oases: [],
});
return `MapDetails:${id}`;
}
}))).filter(content => content !== undefined);
return { valleys, oases, names };
};
const processOases = async (session, valleys, oases, names) => {
const maximum = 999;
const { cache } = await (await fetch(`https://${window.location.hostname}/api/?c=cache&a=get`, {
method: 'POST',
body: JSON.stringify({ controller: 'cache', action: 'get', params: { names, session } }),
})).json();
cache.map(({ name, data: { isOasis, isHabitable, oasisStatus, hasVillage, hasNPC, troops: { units } = { units: {} } } }) => {
const innerExternalId = parseInt(name.split(':')[1]);
if (isHabitable) {
const valley = valleys.filter(({ externalId }) => externalId === innerExternalId)[0];
valley.occupied = parseInt(hasVillage) === 1 || parseInt(hasNPC) === 1;
}
if (isOasis && !['1'].includes(oasisStatus)) {
const oasis = oases.filter(({ externalId }) => externalId === innerExternalId)[0];
if (units[10]) {
oasis.elephant = parseInt(units[10]);
oasis.elephant = oasis.elephant > maximum ? maximum : oasis.elephant;
}
if (units[9]) {
oasis.tiger = parseInt(units[9]);
oasis.tiger = oasis.tiger > maximum ? maximum : oasis.tiger;
}
if (units[8]) {
oasis.crocodile = parseInt(units[8]);
oasis.crocodile = oasis.crocodile > maximum ? maximum : oasis.crocodile;
}
if (units[7]) {
oasis.bear = parseInt(units[7]);
oasis.bear = oasis.bear > maximum ? maximum : oasis.bear;
}
if (units[6]) {
oasis.wolf = parseInt(units[6]);
oasis.wolf = oasis.wolf > maximum ? maximum : oasis.wolf;
}
if (units[5]) {
oasis.boar = parseInt(units[5]);
oasis.boar = oasis.boar > maximum ? maximum : oasis.boar;
}
if (units[4]) {
oasis.bat = parseInt(units[4]);
oasis.bat = oasis.bat > maximum ? maximum : oasis.bat;
}
if (units[3]) {
oasis.snake = parseInt(units[3]);
oasis.snake = oasis.snake > maximum ? maximum : oasis.snake;
}
if (units[2]) {
oasis.spider = parseInt(units[2]);
oasis.spider = oasis.spider > maximum ? maximum : oasis.spider;
}
if (units[1]) {
oasis.rat = parseInt(units[1]);
oasis.rat = oasis.rat > maximum ? maximum : oasis.rat;
}
}
});
oases.sort((one, two) => {
let sortation = undefined;
['elephant', 'tiger', 'crocodile', 'bear', 'wolf', 'boar', 'bat', 'snake', 'spider', 'rat'].map(animal => {
if (one[animal] !== two[animal] && !sortation) {
sortation = one[animal] > two[animal] ? -1 : 1;
}
});
return sortation ? sortation : one.distance > two.distance ? 1 : -1;
});
return { valleys, oases };
};
const processValleys = (valleys, oases) => {
const innerValleys = valleys.map(valley => {
const bonuses = [];
oases
.filter(({ x, y }) => x > valley.x - 4 && x < valley.x + 4 && y > valley.y - 4 && y < valley.y + 4)
.map(({ type }) => {
switch (type) {
case 41:
bonuses.push(50);
break;
case 40:
case 31:
case 21:
case 11:
bonuses.push(25);
break;
default:
bonuses.push(0);
break;
}
valley.oases.push({ type });
});
bonuses.sort((one, two) => two - one);
valley.oases.sort(({ type: oneType }, { type: twoType }) => {
if ([41, 31, 21, 11].includes(twoType)) {
return [41, 31, 21, 11].includes(oneType) ? twoType - oneType : 1;
} else {
return [41, 31, 21, 11].includes(oneType) ? -1 : twoType - oneType;
}
});
return { ...valley, bonus: bonuses.slice(0, 3).reduce((accumulator, bonus) => accumulator + bonus, 0) };
});
innerValleys.sort((
{ type: oneType, bonus: oneBonus, distance: oneDistance, occupied: oneOccupied },
{ type: twoType, bonus: twoBonus, distance: twoDistance, occupied: twoOccupied },
) => {
if (oneOccupied !== twoOccupied) {
return oneOccupied - twoOccupied;
} else {
if (oneType !== twoType) {
return twoType - oneType;
} else {
return oneBonus !== twoBonus ? twoBonus - oneBonus : oneDistance - twoDistance;
}
}
},
);
return innerValleys;
};
const createFooter = () => {
const body = document.querySelector('body');
const text = document.createElement('div');
text.innerText = 'Search for valleys and oases';
const node = document.createElement('div');
node.style.position = 'fixed';
node.style.bottom = '0';
node.style.right = '0';
node.style.left = '0';
node.style.backgroundColor = '#000000';
node.style.color = '#FFFFFF';
node.style.lineHeight = '25px';
node.style.textAlign = 'center';
node.style.cursor = 'pointer';
node.style.zIndex = '10000';
node.classList.add('travian-kingdoms-tools');
const nodeOne = document.createElement('div');
nodeOne.style.display = 'none';
nodeOne.style.float = 'left';
nodeOne.style.width = '50%';
nodeOne.style.position = 'fixed';
nodeOne.style.bottom = '25px';
nodeOne.style.right = '50%';
nodeOne.style.left = '0';
nodeOne.style.height = '279px';
nodeOne.style.maxHeight = '279px';
nodeOne.style.backgroundColor = '#000000';
nodeOne.style.color = '#FFFFFF';
nodeOne.style.lineHeight = '25px';
nodeOne.style.textAlign = 'center';
nodeOne.style.cursor = 'pointer';
nodeOne.style.zIndex = '10000';
nodeOne.style.border = '1px solid #000000';
nodeOne.style.overflowY = 'scroll';
nodeOne.classList.add('travian-kingdoms-tools-table-one');
const nodeTwo = document.createElement('div');
nodeTwo.style.display = 'none';
nodeTwo.style.float = 'right';
nodeTwo.style.width = '50%';
nodeTwo.style.position = 'fixed';
nodeTwo.style.bottom = '25px';
nodeTwo.style.right = '0';
nodeTwo.style.left = '50%';
nodeTwo.style.height = '279px';
nodeTwo.style.maxHeight = '279px';
nodeTwo.style.backgroundColor = '#000000';
nodeTwo.style.color = '#FFFFFF';
nodeTwo.style.lineHeight = '25px';
nodeTwo.style.textAlign = 'center';
nodeTwo.style.cursor = 'pointer';
nodeTwo.style.zIndex = '10000';
nodeTwo.style.border = '1px solid #000000';
nodeTwo.style.overflowY = 'scroll';
nodeTwo.classList.add('travian-kingdoms-tools-table-two');
node.appendChild(text);
body.appendChild(node);
body.appendChild(nodeOne);
body.appendChild(nodeTwo);
return { text, node, nodeOne, nodeTwo };
};
const createTables = () => {
const valleyTable = document.createElement('table');
valleyTable.style.color = '#000000';
valleyTable.style.border = '0';
valleyTable.style.cursor = 'default';
const oasisTable = document.createElement('table');
oasisTable.style.color = '#000000';
oasisTable.style.border = '0';
oasisTable.style.cursor = 'default';
return { valleyTable, oasisTable };
};
const createRow = (table, contentOne, contentTwo) => {
const row = table.insertRow();
const cellOne = row.insertCell();
cellOne.style.width = '25px';
cellOne.style.borderRight = '1px solid #000000';
cellOne.style.borderBottom = '1px solid #000000';
cellOne.style.textAlign = 'center';
cellOne.innerHTML = contentOne.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
const cellTwo = row.insertCell();
cellTwo.style.height = '25px';
cellTwo.style.borderRight = '1px solid #000000';
cellTwo.style.borderBottom = '1px solid #000000';
cellTwo.innerHTML = contentTwo;
};
const createValleyContent = ({ externalId, type, oases, bonus, distance, occupied }) => {
const mapper = {
41: { name: '50%', position: '-286px -376px', opacity: '1.0' },
40: { name: '25%', position: '-286px -376px', opacity: '0.5' },
31: { name: '25% + 25%', position: '-132px -376px', opacity: '1.0' },
30: { name: '25%', position: '-132px -376px', opacity: '0.5' },
21: { name: '25% + 25%', position: '-242px -376px', opacity: '1.0' },
20: { name: '25%', position: '-242px -376px', opacity: '0.5' },
11: { name: '25% + 25%', position: '-198px -376px', opacity: '1.0' },
10: { name: '25%', position: '-198px -376px', opacity: '0.5' },
};
const content = oases.map(oasis => {
const { name, position, opacity } = mapper[oasis.type];
return `<div title='${name}' style='display: inline-block; width: 22px; height: 22px; margin: 0 auto; vertical-align: -4px; background-image: url("./layout/images/sprites/general.png"); background-position: ${position}; opacity: ${opacity}'></div>`;
}).join(' ');
return `<a href="https://${window.location.hostname}/#/page:map/window:mapCellDetails/cellId:${externalId}/centerId:${externalId}" style="color: ${occupied ? '#FF0000' : '#008800'}">The
${type.toString().padStart(5, '0')} valley</a> is
${distance.toString().padStart(3, '0')} fields away with
${bonus.toString().padStart(3, '0')}% ${content}`;
};
const createOasisContent = oasis => {
const { externalId, distance } = oasis;
const content = [
{ key: 'elephant', value: 'Elephant', position: '-40px -80px' },
{ key: 'tiger', value: 'Tiger', position: '-20px -100px' },
{ key: 'crocodile', value: 'Crocodile', position: '0 -100px' },
{ key: 'bear', value: 'Bear', position: '-100px -80px' },
{ key: 'wolf', value: 'Wolf', position: '-100px -60px' },
{ key: 'boar', value: 'Boar', position: '-100px -40px' },
{ key: 'bat', value: 'Bat', position: '0 0' },
{ key: 'snake', value: 'Snake', position: '-100px 0' },
{ key: 'spider', value: 'Spider', position: '-80px -80px' },
{ key: 'rat', value: 'Rat', position: '-60px -80px' },
].map(({ key, value, position }) => oasis[key] ? `${oasis[key].toString().padStart(3, '0')}
<div title='${value}' style='display: inline-block; width: 20px; height: 20px; margin: 0 auto; vertical-align: -4px; background-image: url("./layout/images/sprites/unit/small/unit/small.png"); background-position: ${position};'></div>` : undefined,
).filter(content => content !== undefined).join(' ');
return `<a href="https://${window.location.hostname}/#/page:map/window:mapCellDetails/cellId:${externalId}/centerId:${externalId}" style="color: ${content ? '#008800' : '#FF0000'}">The oasis</a> is
${distance.toString().padStart(3, '0')}
fields away ${content ? `with ${content}` : ''}`;
};