/**
* Script Name: Watchtower Timer
* Last Updated: 2020-07-15
* Support thread: https://forum.tribalwars.net/index.php?threads/watchtower-timer.285084/
* Authors:
* - öreg / silicina https://forum.tribalwars.net/index.php?members/oreg.123551/
* - DaWolf85 https://forum.tribalwars.net/index.php?members/dawolf85.124635/
* - Alex https://forum.tribalwars.net/index.php?members/frying-pan-warrior.124633/
*
* Use babel to compile to js compatible with all browsers. https://babeljs.io
*
* TODO:
* Replace ajax call nesting with promises.
*
* Add Columns
* - Watch Tower coords
* - Coordinate location of the attack at the time the script was run
*
* [Changelog]
* - Original Script by öreg / silicina on Hungarian forum https://forum.klanhaboru.hu/index.php?threads/%C5%90rtorony-bel%C3%A9p%C3%A9s-a-hat%C3%B3sug%C3%A1rba.4895/
*
* - DaWolf85 Added compatibility for .net and fixed some problems
*
* - Alex started recode using classes to introduce code reusability and oop concepts and potential to remove ajax call nesting
*
*/
if (!document.URL.match("mode=incomings&type=unignored&subtype=attacks")) {
let incomingsScreenUrl = game_data.link_base_pure.replace(/screen\=\w*/i, "screen=overview_villages&mode=incomings&type=unignored&subtype=attacks");
self.location.assign(incomingsScreenUrl);
}
class UnitSpeedConfig {
spearSpeed;
swordSpeed;
axeSpeed;
scoutSpeed;
lightCavSpeed;
heavyCavSpeed;
ramSpeed;
catSpeed;
nobleSpeed;
paladinSpeed;
constructor(configXML) {
let configNode = "config";
let speedNode = "speed";
let spearNode = "spear";
let swordNode = "sword";
let axeNode = "axe";
let scoutNode = "spy";
let lightCavNode = "light";
let heavyCavNode = "heavy";
let ramNode = "ram";
let catNode = "catapult";
let nobleNode = "snob";
let paladinNode = "knight";
let configRoot = $(configXML);
function getSpeed(unitName) {
return Number(configRoot.find(configNode + " > " + unitName + " > " + speedNode));
}
this.spearSpeed = getSpeed(spearNode);
this.swordSpeed = getSpeed(swordNode);
this.axeSpeed = getSpeed(axeNode);
this.scoutSpeed = getSpeed(scoutNode);
this.lightCavSpeed = getSpeed(lightCavNode);
this.heavyCavSpeed = getSpeed(heavyCavNode);
this.ramSpeed = getSpeed(ramNode);
this.catSpeed = getSpeed(catNode);
this.nobleSpeed = getSpeed(nobleNode);
this.paladinSpeed = getSpeed(paladinNode);
}
}
class WatchTower {
location;
radius;
constructor(location, level) {
let radius = WatchTowerConfig.getTowerRadius(level);
this.location = location;
this.radius = radius;
}
}
class Command {
origin;
destination;
distance;
speed;
timeLeft;
constructor(origin, destination, distance, speed, timeLeft) {
this.origin = origin;
this.destination = destination;
this.distance = distance;
this.speed = speed;
this.timeLeft = timeLeft;
}
}
class Coordinate {
x;
y;
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class Line {
y;
m;
x;
b;
}
class Circle {
r;
x;
a;
y;
b;
}
var WatchTowerConfig = {};
WatchTowerConfig.levelToRadius = new Map();
WatchTowerConfig.levelToRadius.set(1, 1.1);
WatchTowerConfig.levelToRadius.set(2, 1.3);
WatchTowerConfig.levelToRadius.set(3, 1.5);
WatchTowerConfig.levelToRadius.set(4, 1.7);
WatchTowerConfig.levelToRadius.set(5, 2);
WatchTowerConfig.levelToRadius.set(6, 2.3);
WatchTowerConfig.levelToRadius.set(7, 2.6);
WatchTowerConfig.levelToRadius.set(8, 3);
WatchTowerConfig.levelToRadius.set(9, 3.4);
WatchTowerConfig.levelToRadius.set(10, 3.9);
WatchTowerConfig.levelToRadius.set(11, 4.4);
WatchTowerConfig.levelToRadius.set(12, 5.1);
WatchTowerConfig.levelToRadius.set(13, 5.8);
WatchTowerConfig.levelToRadius.set(14, 6.7);
WatchTowerConfig.levelToRadius.set(15, 7.6);
WatchTowerConfig.levelToRadius.set(16, 8.7);
WatchTowerConfig.levelToRadius.set(17, 10);
WatchTowerConfig.levelToRadius.set(18, 11.5);
WatchTowerConfig.levelToRadius.set(19, 13.1);
WatchTowerConfig.levelToRadius.set(20, 15);
WatchTowerConfig.getTowerRadius = function (level) {
return WatchTowerConfig.levelToRadius.get(level);
};
var towers = [];
var commands = [];
var intersectionPoints = [];
var block = [];
var timesRun = 1;
var rowCount = getIncomingCount();
addWatchTowerColumn();
function getIncomingCount() {
//Finds the 'Commands (x)' table header and removes everything except the number
return Number(
incomingsTableNode
.find("th")
.first()
.text()
.replace("Command ", "")
.replace("(", "")
.replace(")", "")
);
}
function extractTowers(data) {
let coordRegex = /(\d{3})\|(\d{3})/;
let watchTowers = [];
$(data).find("#villages").find("tr").each(function(key, val) {
let tableRow = $(val);
let level = Number(tableRow.find(".upgrade_building.b_watchtower").text());
if (level > 0) {
let coordX = tableRow.find(".quickedit-label").text().match(coordRegex)[0];
let coordY = tableRow.find(".quickedit-label").text().match(coordRegex)[1];
let coord = new Coordinate(coordX, coordY);
let watchTower = new WatchTower(coord, level);
watchTowers.push(watchTower);
}
});
if (watchTowers.length === 0) {
UI.ErrorMessage("There are no watchtowers in any of your villages!", 5000)
}
return watchTowers;
}
function addWatchTowerColumn() {
incomingsTableNode
.find("tr")
.eq(0)
.find("th")
.last()
.after('<th>Watchtower</th>');
}
function loadUnitSpeeds() {
var unitInfoUrl = "https://" + location.host + "/interface.php?func=get_unit_info";
$.ajax({
url: unitInfoUrl,
success: onConfigLoadCompletion
});
}
function loadWatchTowers() {
var buildingOverviewUrl = "https://" + location.host + game_data.link_base_pure + "overview_villages&mode=buildings&group=0&page=-1";
$.ajax({
url: buildingOverviewUrl,
success: onBuildingOverviewLoadCompletion
});
}
function onConfigLoadCompletion(data) {
unitSpeeds = new UnitSpeedConfig(data);
}
function onBuildingOverviewLoadCompletion(data) {
towers = extractTowers(data);
}
function extractCommands() {
let commands = [];
let incomingsTableNode = $("#incomings_table");
let coordRegex = /(\d{3})\|(\d{3})/;
let troopRegex = /(spear|sword|axe|spy|scout|lcav|light|hcav|heavy|ram|cat|catapult|noble|snob)/i;
incomingsTableNode.find("tr")
.each(function (index) {
if (index !== 0) {
let rowDatas = this.find("td");
let speed = rowDatas.eq(0).text().trim().toLowerCase().match(troopRegex)[0];
let destinationX = Number(rowDatas.eq(1).text().match(coordRegex)[0]);
let destinationY = Number(rowDatas.eq(1).text().match(coordRegex)[1]);
let originX = Number(rowDatas.eq(2).text().match(coordRegex)[0]);
let originY = Number(rowDatas.eq(2).text().match(coordRegex)[1]);
let distance = Number(rowDatas.eq(4).text().trim());
let hoursMinutesSeconds = rowDatas.eq(6).text().split(':');
let destination = new Coordinate(destinationX, destinationY);
let origin = new Coordinate(originX, originY);
let seconds = hoursMinutesSeconds[0] * 3600 + hoursMinutesSeconds[1] * 60 + hoursMinutesSeconds[2];
let command = new Command(origin, destination, distance, speed, seconds);
commands.push(command);
}
});
return commands;
}
function getIntersectionPoints(line, circle) {
let
}
function findCircleLineIntersections(r, h, k, m, n) {
// circle: (x - h)^2 + (y - k)^2 = r^2
// line: y = m * x + n
// r: circle radius
// h: circle x coords
// k: circle y coords
// m: line slope
// n: y-intercept
// a, b, c is (?)
var a = 1 + Math.pow(m, 2);
var b = -h * 2 + (m * (n - k)) * 2;
var c = Math.pow(h, 2) + Math.pow(n - k, 2) - Math.pow(r, 2);
// discriminatory value (?)
var d = Math.pow(b, 2) - 4 * a * c;
if (d >= 0) {
// quadratic formula
var intersections = [
(-b + Math.sqrt(d)) / 2 / a,
(-b - Math.sqrt(d)) / 2 / a
];
if (d === 0) {
// the tangent of the line to the circle (a common point) (?)
intersectionPoints.push((Number(intersections[0])) + "|" + (Number(m * intersections[0] + n)));
}
// the line intersects the outline (two common points) (?)
intersectionPoints.push((Number(intersections[0])) + "|" + (Number(m * intersections[0] + n)));
intersectionPoints.push((Number(intersections[1])) + "|" + (Number(m * intersections[1] + n)));
}
// nothing in common (?)
}
function processCommand(command) {
intersectionPoints = [];
block = [];
// add a row to the column
$("#incomings_table").find("tr").eq(timesRun).find("td").last().after("<td></td>");
// the slope of the line is m = (y1-y2) / (x1-x2), if the divisor is zero, then the divisor should be equal to 1
let rise = command.destination.y - command.origin.y;
let run = command.destination.x - command.origin.x;
run = run !== 0 ? run : 1;
var slope = rise / run;
// where the line intersects the y axis y1 = mx1 + b
var n = (slope * Number(target[0]) - Number(target[1])) / -1;
for (const tower of towers) {
}
for (var i = 0; i < towerCoords.length; i++) {
var h = (String(towerCoords[i]).split("|"))[0];
var k = (String(towerCoords[i]).split("|"))[1];
var r = towerLevels[i];
findCircleLineIntersections(r, h, k, slope, n);
}
//console.log(intersectionPoints.length);
// if no common point
if (intersectionPoints.length == 0) {
$("#incomings_table").find("tr").eq(timesRun).find("td").last().text("Undetectable").css({
"font-weight": "bold",
"color": "red"
});
++timesRun
setTimeout(doStuff, 1);
}
// intersection closest to origin village on circle
for (var i = 0; i < intersectionPoints.length; i++) {
var intersections = intersectionPoints[i].split("|");
// for each intersection, calculate distance to origin village
var originDistance = Math.sqrt((Math.pow((intersections[0] - source[0]), 2) + Math.pow((intersections[1] - source[1]), 2)));
block.push(originDistance);
}
//console.log(block);
// find index of shortest distance
idx = block.indexOf(Math.min.apply(null, block));
//console.log(idx);
// with the index we get, which is the closest intersection point to the village of origin (?)
var nearest = intersectionPoints[idx];
//console.log(nearest);
// where we are going, i.e. (full distance - remaining field)
var currentDistance = distance - remainingFields;
// (from the distance of the village of origin and the nearest intersection point on the circle) we subtract the (where we go) (?)
// so we get how many squares the attack is from the intersection of the circle and then we convert this to seconds (multiply by the unit speed)
var M = nearest.split("|");
var remaining = Math.sqrt((Math.pow((M[0] - source[0]), 2) + Math.pow((M[1] - source[1]), 2))) - currentDistance;
//console.log(remaining);
if (commandName.includes("sword")) {
var sec = remaining * unitSpeed[0];
} else if (commandName.includes("axe") || commandName.includes("spear")) {
var sec = remaining * unitSpeed[1];
} else if (commandName.includes("spy") || commandName.includes("scout")) {
var sec = remaining * unitSpeed[2];
} else if (commandName.includes("lcav") || commandName.includes("light")) {
var sec = remaining * unitSpeed[3];
} else if (commandName.includes("hcav") || commandName.includes("heavy")) {
var sec = remaining * unitSpeed[4];
} else if (commandName.includes("ram") || commandName.includes("cat")) {
var sec = remaining * unitSpeed[5];
}else if (commandName.includes("noble") || commandName.includes("snob")) {
var sec = remaining * unitSpeed[6];
}
// count down in seconds
var myTimer;
function clock(x) {
myTimer = setInterval(myClock, 1000);
function myClock() {
--sec
var seconds = Math.floor(sec % 60);
var minutes = Math.floor((sec / 60) % 60);
var hours = Math.floor((sec / (60 * 60)));
// if the number is less than 10, you enter 0
seconds = seconds < 10 ? "0" + seconds : seconds;
minutes = minutes < 10 ? "0" + minutes : minutes;
hours = hours < 10 ? "0" + hours : hours;
time = hours + ":" + minutes + ":" + seconds;
// add time to table
if (sec < 0) {
// hif the attack is within range, the sec is negative
var time = "Detected";
$("#incomings_table").find("tr").eq(x).find("td").last().text(time).css({
"font-weight": "bold",
"color": "green"
});
} else {
var time = hours + ":" + minutes + ":" + seconds;
$("#incomings_table").find("tr").eq(x).find("td").last().text(time).css("font-weight", "bold");
}
if (sec == 0) {
clearInterval(myTimer);
}
}
}
clock(timesRun);
//console.log(towerCoords);
//console.log(towerLevels);
//console.log(distance);
//console.log(destination);
//console.log(origin);
//console.log(unitSpeed);
//console.log(remainingFields);
//console.log(m);
//console.log(n);
//console.log(h);
//console.log(k);
//console.log(intersectionPoints);
//console.log(sec);
if (++timesRun < rowCount + 1) {
doStuff();
}
//},
//})
}
$.ajax({url: buildingOverviewUrl, success: function() {
$.ajax({
url: first(),
success: function() {
doStuff();
}
})
}
});
void(0);