DaWolf85

Senior In-Game Staff
Tribal Wars Team
Senior
Team
Reaction score
570
Author
Dawolf85
Contributors
oreg/silicina
Quickbar Entry
javascript:$.getScript('https://dl.dropboxusercontent.com/s/dukcaol8u27wxg2/watchtower_timer.js');void(0);
Public?
Public
This is a fully working, English-translated version of this watchtower timer script from oreg/silicina on the .hu forums.

I would like to thank lodi94 for giving me the idea with his version for both the Italian and International versions. Unfortunately, his version does not work for me, and his code is too obfuscated to try and fix - so I went back to the source, fixed it up a bit, translated it to English, and got it approved for .net (ticket number t13893927).

For anyone looking to edit this script in the future - some of the comments I could not translate perfectly (as I do not speak Hungarian, I used Google Translate). For these, I have attached a (?) to the end of the comment, to denote questionable English.


Description:

This script will calculate when an incoming attack will enter your watchtower's range, and display a countdown for each incoming you have. It will create a new column for this, labeled "Watchtower".

If the incoming is already detected by a watchtower, the script will print "Detected". If the incoming will not fall inside a watchtower's range, it will print "Undetectable". Otherwise, it will show a countdown to the time the attack will be detected by a watchtower.


Usage:
  1. Click the script to be taken to the appropriate incomings page. It requires that you be on the 'unignored' incomings page, so even if you think you are on the incomings page, it may still refresh the page to take you where it wants to be.
  2. Click the script again to actually run it.
Additionally, you must make sure your incomings are tagged IN ENGLISH, or the script will not be able to detect their speed. To see the tags the script checks for, open the spoiler below.
  • Sword-speed attacks should include "Sword" in their name.
  • Axe/Spear-speed attacks should include "Axe" or "Spear" in their name.
  • Scout-speed attacks should include "Scout" or "Spy" in their name.
  • Light Cavalry-speed attacks should include "LCav" or "Light" in their name.
  • Heavy Cavalry-speed attacks should include "HCav" or "Heavy" in their name.
  • Ram/Cat-speed attacks should include "Ram", "Cat", or "Catapult" in their name.
  • Noble-speed attacks should include "Noble" in their name.
These expected tags are not case-sensitive.
If an incoming's name includes two of these tags, the script will pick the first one from this list.


With all of that out of the way, here is the script itself:
Code:
javascript:$.getScript('https://dl.dropboxusercontent.com/s/dukcaol8u27wxg2/watchtower_timer.js');void(0);
 
Last edited:
Upvote 10

Frying Pan Warrior

Still Going Strong
Reaction score
565
Here is my work in progress in case anyone wants to finish it. Note the TODO: I was wanting to add a column that shows the coordinates of the watch tower that will detect the attack, as well as a column that shows an estimated coordinate of WHERE on the map the attack is currently located which would be an interesting feature I've never heard of a script ever do. Just too bad it won't show on the map as an icon.

It will probably require a new ticket to verify its legality.

JavaScript:
/**
 * 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);
 

Shinko to Kuma

Still Going Strong
Reaction score
772
Here is my work in progress in case anyone wants to finish it. Note the TODO: I was wanting to add a column that shows the coordinates of the watch tower that will detect the attack, as well as a column that shows an estimated coordinate of WHERE on the map the attack is currently located which would be an interesting feature I've never heard of a script ever do. Just too bad it won't show on the map as an icon.

It will probably require a new ticket to verify its legality.

JavaScript:
/**
* 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);

Drawing where the attack is on the map wouldn't be that hard to do, I just don't see a way to make it efficient, since you'd get so many icons it would be super cluttered.

I'm working on some code to display attack lines, transports, watchtowers and stuff on the map right now. Just trying to figure out how to get tooltips to work, if everything was one canvas that would be easy, but since every canvas is seperate (and as such every additional drawing needs to be done seperate too), it becomes a pain

Images of attack lines, and watchtowers in progress (Theoretically I can just place the attack in the estimated coordinate location if I were to place an icon, I placed a test text as an example right in the middle coordinate between the origin/target)

GdmzP.png
 
Last edited:

Txitxo

blocked
Reaction score
33
not sure how accurate this is however, with some tribal wars help page:
level 1 WT = 1.1 => 35minutes x 0.1= 3.5minutes - 3 minutes 30 seconds before the attack arrives.

Hope this can help shall the script not work or shall you not have premium account when you have incomings.
 

DaWolf85

Senior In-Game Staff
Tribal Wars Team
Senior
Team
Reaction score
570
Hello,

is this still working for everyone?
Because I got calculated only first attack.

Am I doing something wrong?
Please help.

I'm playing on Slovak 62 server.

Thanks.
Thanks for reporting this. After a long time, I got another report on Discord, which included a small nugget of additional useful information, allowing me to finally track down and resolve this bug. The issue was that the script only worked on the English language versions of Tribal Wars (specifically, it looked for the word "Commands" on the incomings page). It should now work on any version.
 

Frying Pan Warrior

Still Going Strong
Reaction score
565
Some attacks show as detected that aren't detected yet perhaps because a WT that wasnt there before is there now. It should probably check the command icon to determine if it should calculate the attack's position and do a new distance check to the next WT.
 

DaWolf85

Senior In-Game Staff
Tribal Wars Team
Senior
Team
Reaction score
570
Some attacks show as detected that aren't detected yet perhaps because a WT that wasnt there before is there now. It should probably check the command icon to determine if it should calculate the attack's position and do a new distance check to the next WT.

It checks your buildings each time the script runs, so that doesn't sound right. I have noticed that there is some sort of fudging that the game does as far as when an attack is detected (like perhaps it only detects at certain distance intervals), but that should only be a few minutes difference.
 
Top