UD Travel

Whenever there is a groan or flare, you can click on a link that will take you there. You can type in coordinates to travel faster.

// ==UserScript==
// @name           UD Travel
// @namespace      www.ray3k.com
// @description    Whenever there is a groan or flare, you can click on a link that will take you there. You can type in coordinates to travel faster.
// @include        http://www.urbandead.com/map.cgi*
// @include        http://*urbandead.com/map.cgi*
// @exclude        http://www.urbandead.com/map.cgi?logout
// @exclude        http://*urbandead.com/map.cgi?logout
// @version 0.0.1.20160405031941
// ==/UserScript==

//create the travel form to allow manual entry
createTravelForm();
//transform all groan and flare messages to links that will travel the player
transformGroansFlares();

//finds the donation link on the page
function findDonateLink() {
    //find the donate link
    var allDonateLinks, donateLink;
    allDonateLinks = document.evaluate(
        "//a[@href='donate.html']",
        document,
        null,
        XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
        null);
    
    //if the donate link was found
    if (allDonateLinks.snapshotLength > 0) {
        donateLink = allDonateLinks.snapshotItem(0);
        return donateLink;
    }
    //the donate link was not found
    else return null;
}

//finds the map table on the page
function findMapTable() {
    //find the map table
    var allMapTables, mapTable;
    allMapTables = document.evaluate(
        "//table[@class='c']",
        document,
        null,
        XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
        null);
    
    //if the map table was found
    if (allMapTables.snapshotLength > 0) {
        mapTable = allMapTables.snapshotItem(0);
        return mapTable
    }
    //the donate link was not found
    else return null;
}

//creates the Travel Form() and places itself on to the page
function createTravelForm() {
    //find the donation link
    var donateLink = findMapTable();
    //if the donate link was found
    if (donateLink != null) {
        //create the travel form
        var travelForm = document.createElement("form");
        travelForm.className = "a";
        travelForm.id = "travelform";

        //create the travel button
        var travelButton = document.createElement("input");
        travelButton.type = "submit";
        travelButton.value = "Travel";
        travelButton.className = "m";

        //create the field for x position
        var xInput, yInput;
        xInput = document.createElement("input");
        xInput.type = "text";
        xInput.maxLength = 2;
        xInput.size = 2;

        //create the field for y position
        yInput = document.createElement("input");
        yInput.type = "text";
        yInput.maxLength = 2;
        yInput.size = 2;

        //add the elements to the form
        travelForm.appendChild(travelButton);
        travelForm.appendChild(document.createTextNode(" "));
        travelForm.appendChild(xInput);
        travelForm.appendChild(document.createTextNode(" "));
        travelForm.appendChild(yInput);

        //create an event listener to respond to the clicking of the travel button
        travelButton.addEventListener("click", function(event) {
            //travel to the manually entered values
            travelTo(xInput.value, yInput.value);
            //prevent the normal operation of the button
            event.stopPropagation();
            event.preventDefault();
        }, true);

        //insert the travelLink after the donateLink
        donateLink.parentNode.insertBefore(travelForm, donateLink.nextSibling);
        //insert a space character between donate and travel
        donateLink.parentNode.insertBefore(document.createTextNode(" "), donateLink.nextSibling);
    }
}

//returns the travel form element
function findTravelForm() {
    return document.getElementById("travelform");
}

//xpos = target x;
//ypos = target y;
function travelTo(xpos, ypos) {
    //x and y are the player's current position. xpos and ypos are the target position
    var x, y;
    
    //get the current position
    var position = getGPSLoc();
    x = position[0];
    y = position[1];
    
    //create a text node to show status
    var textNode = document.createTextNode("Travelling...");
    
    //place the text node in place of the travel form
    var travelForm = findTravelForm();
    travelForm.parentNode.insertBefore(textNode, travelForm);
    travelForm.parentNode.removeChild(travelForm);
    
    //create a variable as reference to check if the player has moved successfully
    var ref = "empty";
    
    //check the values of xpos and ypos
    if (isNaN(xpos) || xpos < 0 || xpos > 99 || isNaN(ypos) || ypos < 0 || ypos > 99) {
        textNode.nodeValue = "Travel target is not valid.";
        //reload the page
        location.replace("http://" + location.hostname + "/map.cgi");
    }
    else {
        //if standing
        if (checkIfStanding()) {
            //start moving the player
            travel();
        }
        //not standing
        else {
            //update the text node
            textNode.nodeValue = "Travelling... Standing up";

            //command the player to stand up
            GM_xmlhttpRequest({
                method: 'GET',
                url: "http://" + location.hostname + '/map.cgi?rise',
                onload: function(responseDetails) {
                    //if standing was successful
                    if (responseDetails.status == 200) {
                        //start moving the player
                        travel();
                    }
                    else {
                        //error when trying to move player
                        textNode.nodeValue = "Failure! Please wait for refresh.";
                        //reload the page
                        location.replace("http://" + location.hostname + "/map.cgi");
                    }
                }
            });
        }
    }
    
    //commands the player to move towards target and ensures the move was successful
    function travel() {
        //if player needs to move east, move east 1
        if (x < xpos) x++;
        //player needs to move west, move west 1
        else if (x > xpos) x--;
        
        //if player needs to move south, move south 1
        if (y < ypos) y++;
        //player needs to move north, move north 1
        else if (y > ypos) y--;
        
        //move player to new position
        GM_xmlhttpRequest({
            method: 'GET',
            url: "http://" + location.hostname + '/map.cgi?v=' + x + '-' + y,
            onload: function(responseDetails) {
                //if the movement was successful
                if (responseDetails.status == 200 && checkMovement(responseDetails.responseText)) {
                    //if player has arrived at the target
                    if (x == xpos && y == ypos) {
                        //notify player
                        textNode.nodeValue = "Arrival at (" + xpos + "," + ypos + ") Please wait for refresh.";
                        //refresh page
                        location.replace("http://" + location.hostname + "/map.cgi");
                    }
                    //player has not arrived at target
                    else {
                        //notify player of current position
                        textNode.nodeValue = "Travelling...(" + x + "," + y + ")";
                        //call travel again
                        travel();
                    }
                }
                //movement was not successful or event in game prevents movement
                else {
                    //error when trying to move player
                    textNode.nodeValue = "Failure! Please wait for refresh.";
                    //refresh page
                    location.replace("http://" + location.hostname + "/map.cgi");
                }
            }
        });
    }

    //checks if the move was succesful by using a reference variable
    function checkMovement(bodyString) {
        //create a new reference
        var newRef = bodyString.match(/"\d{1,2}-\d{1,2}"/);
        
        //if the bodyString found a match and the player did not run out of AP
        if (newRef != null && newRef[0] != null && ref != newRef[0] && bodyString.match("You have run out of Action Points") == null) {
            //update the reference variable
            ref = newRef[0];
            //movement is unhindered.
            return true;
        }
        else {
            //if the target is on the map border
            if ((xpos == 0 && x == 0) || (xpos == 99 && x == 99) || (ypos == 0 && y == 0) || (ypos == 99 && y == 0)) {
                if (bodyString.match("You have run out of Action Points") == null) {
                    //movement is unhindered
                    return true;
                }
                else {
                    return false;
                }
            }
            else {
                //movement has failed
                return false;
            }
        }
    }
}

//returns the players gps location in an int array. -1 will be returned if there
//is an error
function getGPSLoc() {
    var position = new Array();
    
    //find all inputs within the map table
    var allInputs;
    allInputs = document.evaluate(
        "//table[@class='c']//input[@type='hidden']",
        document,
        null,
        XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
        null);
    
    //when all of the map is visible, there should be 8 nodes
    if (allInputs.snapshotLength == 8) {
        var gpsString = allInputs.snapshotItem(0).value;
        position[0] = parseInt(gpsString.match(/\d+/)) + 1;
        position[1] = parseInt(gpsString.replace(/\d+-/, "")) + 1;
    }
    //when at an edge and not a corner, there would be 5 nodes
    else if (allInputs.snapshotLength == 5) {
        var gpsString = allInputs.snapshotItem(0).value;
        //place the first reading as the gps location
        position[0] = parseInt(gpsString.match(/\d+/));
        position[1] = parseInt(gpsString.replace(/\d+-/, ""));
        
        //if at a left edge
        if (position[0] == 0) {
            if (position[1] < 1) {
                gpsString = allInputs.snapshotItem(1).value;
                position[0] = parseInt(gpsString.match(/\d+/));
                if (position[0] == 2) {
                    position[0] = 1;
                    position[1] = 0;
                }
                else {
                    position[0] = 0;
                    position[1] = 1;
                }
            }
            else if (position[1] == 98) {
                position[0] = 1;
                position[1] = 99;
            }
            else {
                position[1] += 1;
            }
        }
        //if at right edge
        else if (position[0] == 98) {
            //correct the x coordinate
            position[0] = 99;
            //correct the y coordinate
            position[1] += 1;
        }
        //if at a top edge
        else if (position[1] == 0) {
            //correct the x coordinate
            position[0] += 1;
        }
        //if at bottom edge
        else if (position[1] == 98) {
            //correct the x coordinate
            position[0] += 1;
            //correct the y coordinate
            position[1] = 99;
        }
    }
    //when in a corner, there would be only 3 nodes
    else if (allInputs.snapshotLength == 3) {
        var gpsString = allInputs.snapshotItem(0).value;
        //place the first reading as the gps location
        position[0] = parseInt(gpsString.match(/\d+/));
        position[1] = parseInt(gpsString.replace(/\d+-/, ""));
        
        //correct the x coordinate
        if (position[0] == 98)  position[0] = 99;
        else if (position[0] == 1) position[0] = 0;
        
        //correct the y coordinate
        if (position[1] == 98) position[1] = 99;
        else if (position[1] == 1) position[1] = 0;
    }
    //error handling
    else {
        position[0] = -1;
        position[1] = -1;
    }
    
    return position;
}

//transforms all groan and flare messages to links that will call travelTo to the
//corresponding position.
function transformGroansFlares() {
    //collect all the li elements
    var groans = document.getElementsByTagName("li");
    for (i = 0; i < groans.length; i++) {
        
        //solve issue with "familiar" groans
        //if the 2nd child is a bold tag
        var boldTag = groans[i].childNodes[1];
        //if it is a bold tag and it matches the word "familiar"
        if (boldTag.nodeName == "B" && boldTag.firstChild.nodeValue.match("familiar")) {
            groans[i].removeChild(boldTag);
            //if the first and second nodes now are text nodes
            if (groans[i].firstChild.nodeValue != null && groans[i].childNodes[1].nodeValue != null) {
                //combine the nodes so the following code will work
                groans[i].firstChild.nodeValue += "FAMILIAR" + groans[i].childNodes[1].nodeValue;
                groans[i].removeChild(groans[i].childNodes[1]);
            }
        }
        
        //get the first child of the li element
        var textNode = groans[i].firstChild;
        //check that the node exists and that the word "groaning" is within the node
        if (textNode != null && (textNode.nodeValue.match(/groaning/) != null || textNode.nodeValue.match(/flare was fired/))) {
            //create an anchor element
            var anchor = document.createElement('a');
            anchor.href = "map.cgi";
            //create an event listener to capture clicks
            anchor.addEventListener("click", function (event) {
                //get the player coordinates
                var position = getGPSLoc();
                var newX = position[0];
                var newY = position[1];
                
                //evaluate the node for an east or west value
                var textString = event.target.firstChild.nodeValue.match(/\d{1,2}.*(west|east)/);
                //if the value was found
                if (textString != null) {
                    //store as string, not array
                    textString = textString[0];
                    //if the groan was from the west
                    if (textString.match(/west/) != null) {
                        //subtract the value from the x coordinate
                        newX -= parseInt(textString.match(/\d{1,2}/)[0]);
                    }
                    //the groan was from the east
                    else {
                        //add the value to the x coordinate
                        newX += parseInt(textString.match(/\d{1,2}/)[0]);
                    }
                }
                
                //evaluate the node for a north or south value FOLLOWING an east or west value
                textString = event.target.firstChild.nodeValue.match(/(west|east).*\d{1,2}.*(north|south)/);
                //if the value was found
                if (textString != null) {
                    //store as string, not array
                    textString = textString[0];
                    //if the groan was from the north
                    if (textString.match(/north/) != null) {
                        //subtract the value from the x coordinate
                        newY -= parseInt(textString.match(/\d{1,2}/)[0]);
                    }
                    //the groan was from the south
                    else {
                        //add the value to the x coordinate
                        newY += parseInt(textString.match(/\d{1,2}/)[0]);
                    }
                }
                //could not find a north or south value following an east or west value
                else {
                    //evaluate the node for a north or south value WITHOUT an east or west value
                    textString = event.target.firstChild.nodeValue.match(/\d{1,2}.*(north|south)/);
                    //if the value was found
                    if (textString != null) {
                        //store as string, not array
                        textString = textString[0];
                        //if the groan was from the north
                        if (textString.match(/north/) != null) {
                            //subtract the value from the x coordinate
                            newY -= parseInt(textString.match(/\d{1,2}/)[0]);
                        }
                        //the groan was from the south
                        else {
                            //add the value to the x coordinate
                            newY += parseInt(textString.match(/\d{1,2}/)[0]);
                        }
                    }
                }
                
                //command the player to travel to the calculated position of the groan
                travelTo(newX, newY);
                
                //stop the regular action from happening
                event.stopPropagation();
                event.preventDefault();
            }, true);
            
            //insert the created anchor as the first element
            groans[i].insertBefore(anchor, textNode);
            //remove the textNode
            groans[i].removeChild(textNode);
            //insert the textNode inside the anchor
            anchor.appendChild(textNode);
        }
    }
}

//returns true if the player is standing
function checkIfStanding() {
    var allInputs, thisLink;
    //get all the inputs with a value of "Stand up"
    allInputs = document.evaluate(
        "//input[@value='Stand up']",
        document,
        null,
        XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
        null);
    //if such an input exists, the player is not standing
    if (allInputs.snapshotLength > 0) return false;
    //the player is not standing
    else return true;
}