BvS_Daily

Assist in daily tasks

// ==UserScript==
// @name           BvS_Daily
// @namespace      SirDuck36
// @description    Assist in daily tasks
// @version        2019.05.25
// @include        http*://*animecubedgaming.com/billy/bvs*
// @include        http*://*animecubed.com/billy/bvs*
// @grant          GM_log
// @grant          GM_addStyle
// ==/UserScript==

// ** NOTE ** - This version uses ES6 features. For the non-ES6 script please visit
//		https://greasyfork.org/en/scripts/383503-bvs-daily-non-es6
 
// 4.14.2011 Initial version by SirDuck36
// 4.19.2011 Major overhaul of the API by SirDuck36
// 4.20.2011 Minor updates and bug fixes from North
// 4.20.2011 Updated ChangeSequence to include a task number
// 4.20.2011 Bug fix for GoPage('glowslinging');
// 4.21.2011 Added several new options for gopage function
// 4.23.2011 Added sequences link -- suggested by WolfX
// 4.23.2011 Add hotkeylock to left and right arrow hotkeys -- suggested by Skarn22
// 4.23.2011 Added support for //@SequenceCode before first task -- suggested by North
// 4.23.2011 Fixed editor window hide to remove lingering select box -- SirDuck36
// 4.23.2011 Added functionality to hide daily window on desired pages -- SirDuck36
// 6.12.2011 Bug fix for ChangeSequence function -- Skarn22
// 6.19.2011 Add newline to ShowMsg and add pizzawitch to GoPage function -- SirDuck36
// 9.28.2014 Added grant permissions -- Channel28
// 11.7.2015 Fix a bug with flags when changing sequences. Thanks to Terrec -- Channel28
// 11.8.2015 Small fix. Thanks to Terrec -- Channel28
// 12.30.2015 Small fix regarding which key is to be pressed (Thanks to DTC) -- Channel28
// 5.19.2017 Now https compatible -- Channel28
// 2.04.2018 Now compatible with GreaseMonkey v4 (Thanks to Takumi) -- Channel28
// 5.25.2019 Added GoPage destinations for Breakfast, Marketplace & Tattoo Parlor. Hiding/unhiding
//		of the window is remembered globally. This can be turned off by changing the 
//		OptGlobalHide below. True to show and false to hide (Thanks to Chrobot) -- Channel28
 
// Options
// GlobalHide: if set, window's hidden/unhidden state is remembered globally
//             instead of per page.
const OptGlobalHide = true;
 
////////////////////////////////////////////////////////////////////////
///////////                LIBRARY CODE                /////////////////
////////////////////////////////////////////////////////////////////////
 
/*
    DOM Storage wrapper class (credits: http://userscripts.org/users/dtkarlsson)
    Constructor:
        var store = new DOMStorage({"session"|"local"}, [<namespace>]);
    Set item:
        store.setItem(<key>, <value>);
    Get item:
        store.getItem(<key>[, <default value>]);
    Remove item:
        store.removeItem(<key>);
    Get all keys in namespace as array:
        var array = store.keys();
*/
 
function DOMStorage(type, namespace) {
    var my = this;
 
    if (typeof(type) != "string")
        type = "session";
    switch (type) {
        case "local": my.storage = localStorage; break;
        case "session": my.storage = sessionStorage; break;
        default: my.storage = sessionStorage;
    }
 
    if (!namespace || typeof(namespace) != "string")
        namespace = "Greasemonkey";
 
    my.ns = namespace + ".";
    my.setItem = function(key, val) {
        try {
            my.storage.setItem(escape(my.ns + key), val);
        }
        catch (e) {
            GM_log(e);
        }
    },
    my.getItem = function(key, def) {
        try {
            var val = my.storage.getItem(escape(my.ns + key));
            if (val)
                return val;
            else
                return def;
        }
        catch (e) {
            return def;
        }
    }
    // Kludge, avoid Firefox crash
    my.removeItem = function(key) {
        try {
            my.storage.setItem(escape(my.ns + key), null);
        }
        catch (e) {
            GM_log(e);
        }
    }
    // Return array of all keys in this namespace
    my.keys = function() {
        var arr = [];
        var i = 0;
        do {
            try {
                var key = unescape(my.storage.key(i));
                if (key.indexOf(my.ns) == 0 && my.storage.getItem(key))
                    arr.push(key.slice(my.ns.length));
            }
            catch (e) {
                break;
            }
            i++;
        } while (true);
        return arr;
    }
}
// End DOM Storage Wrapper class
 
// UI (credits: http://userscripts.org/users/dtkarlsson)
function Window(id, storage)
{
    var my = this;
    my.id = id;
    my.offsetX = 0;
    my.offsetY = 0;
    my.moving = false;
 
    // Window dragging events
    my.drag = function(event) {
        if (my.moving) {
            my.element.style.left = (event.clientX - my.offsetX)+'px';
            my.element.style.top = (event.clientY - my.offsetY)+'px';
            event.preventDefault();
        }
    }
    my.stopDrag = function(event) {
        if (my.moving) {
            my.moving = false;
            var x = parseInt(my.element.style.left);
            var y = parseInt(my.element.style.top);
            if(x < 0) x = 0;
            if(y < 0) y = 0;
            storage.setItem(my.id + ".coord.x", x);
            storage.setItem(my.id + ".coord.y", y);
            my.element.style.opacity = 1;
            window.removeEventListener('mouseup', my.stopDrag, true);
            window.removeEventListener('mousemove', my.drag, true);
        }
    }
    my.startDrag = function(event) {
        if (event.button != 0) {
            my.moving = false;
            return;
        }
        my.offsetX = event.clientX - parseInt(my.element.style.left);
        my.offsetY = event.clientY - parseInt(my.element.style.top);
           
                if (my.offsetY > 27)
                return;
 
        my.moving = true;
            my.element.style.opacity = 0.75;
        event.preventDefault();
        window.addEventListener('mouseup', my.stopDrag, true);
        window.addEventListener('mousemove', my.drag, true);
    }
 
    my.show = function()
    {
    this.element.style.visibility = 'visible';
    }
 
    my.hide = function()
    {
    this.element.style.visibility = 'hidden';
    }
 
    my.reset = function()
    {
    storage.setItem(my.id + ".coord.x", 6);
    storage.setItem(my.id + ".coord.y", 6);
   
    my.element.style.left = "6px";
    my.element.style.top = "6px";
    }  
 
 
    my.element = document.createElement("div");
    my.element.id = id;
    document.body.appendChild(my.element);
    my.element.addEventListener('mousedown', my.startDrag, true);
 
    if (storage.getItem(my.id + ".coord.x"))
        my.element.style.left = storage.getItem(my.id + ".coord.x") + "px";
    else
        my.element.style.left = "6px";
    if (storage.getItem(my.id + ".coord.y"))
        my.element.style.top = storage.getItem(my.id + ".coord.y") + "px";
    else
        my.element.style.top = "6px";
}
// End UI Window implementation
 
 
 
// Make infinite alerts less miserable
var displayAlerts = true;
function alert(msg) {
  if (displayAlerts) {
     if (!confirm(msg)) {
       displayAlerts = false;          
     }
  }
}
 
////////////////////////////////////////////////////////////////////////
///////////             DATA STRUCTURES                /////////////////
////////////////////////////////////////////////////////////////////////
 
 
function dailyData()
{
    // Save the storage object
    this.storage = new DOMStorage("local", "BvSDaily");
    this.namePlayer = document.evaluate("//input[@name='player' and @type='hidden']", document, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue.value;
 
    // List of all sequences stored in memory
    this.sequenceList = "";
 
    // List of all pages on which the daily window is hidden
    this.hidePageList = "";
 
    // Global hide flag
    this.globalHide = false;
 
    // Name and task number of current sequence
    this.sequenceName = "";
    this.taskNum = 0;
 
    // Actual tasklist for this sequence
    this.taskList = new Array();
 
    // Array of string flags to save information... a flag is set if contained in the array
    //     Crude but simple
    this.flagStr = "|";
 
    // Helper function to set a flag
    this.SetFlag = function(flagtext)
    {
    if (!this.TestFlag(flagtext))
        this.flagStr += flagtext + "|";
    }
 
    // Helper function to test for a flag
    this.TestFlag = function(flagtext)
    {
    if (this.flagStr.indexOf("|" + flagtext + "|") > -1)
        return true;
    else
        return false;
    }
 
    // Check whether a page is in the hidePageList
    this.TestHidePage = function(pageName)
    {
    if (OptGlobalHide)
        return this.globalHide;
    for (var i=0; i < this.hidePageList.length; i++)
        if (this.hidePageList[i] === pageName)
        return true;
    return false;
    }
 
    // Add a page to the hidePageList
    this.AddHidePage = function(pageName)
    {
    if (!this.TestHidePage(pageName))
        this.hidePageList.push(pageName);
    if (OptGlobalHide)
        this.globalHide = true;
    this.SaveState();
    }
 
    // Remove a page from the hidePageList
    this.RemoveHidePage = function(pageName)
    {
    this.globalHide = false;
    for (var i=0; i < this.hidePageList.length; i++)
        if (this.hidePageList[i] === pageName)
        {
        this.hidePageList.splice(i,1);
        break;
        }
    this.SaveState();
    }
 
    // Check whether a sequence name is in the sequence list
    this.TestSequence = function(sequenceName)
    {
    for (var i=0; i < this.sequenceList.length; i++)
        if (this.sequenceList[i] === sequenceName)
        return true;
    return false;
    }
 
    // Remove a sequence
    this.RemoveSequence = function(sequenceName)
    {
    // Remove sequence from sequence list
    for (var i=0; i<this.sequenceList.length; i++)
        if (this.sequenceList[i] === sequenceName)
        {
        // Remove this element from sequencelist
        this.sequenceList.splice(i,1);
        break;
        }
 
    this.SaveState();
 
    this.storage.removeItem("Daily_Sequence_" + sequenceName);
    }
 
    // Store a new or modified sequence
    this.SaveSequence = function(sequenceName, sequenceCode)
    {
    if (!this.TestSequence(sequenceName))
    {
        // Add sequenceName to sequenceList and save
        this.sequenceList.push(sequenceName);
        this.SaveState();
    }
   
    // Save the sequence code
    this.storage.setItem("Daily_Sequence_" + sequenceName, sequenceCode);
 
    // Update the daily window
    floatingDaily.Draw();
    }
 
    // Load and parse a given new sequence name
    this.LoadSequence = function(newSequenceName)
    {
    // Set the current sequence name and reinitialize taskList
 
    this.flagStr = this.storage.getItem(this.namePlayer + "_Daily_" + newSequenceName + "_Flags", "|");
 
    if (!(this.sequenceName === newSequenceName))
    {
        // Only reset the sequencename and tasknum if this is not the current sequence in use
        this.sequenceName = newSequenceName;
        this.taskNum = 1;
    }
   
    // Initialize empty tasklist
    this.taskList = new Array();
 
    // Load newSequenceName from memory and parse into this.taskList
    var strSequenceData = this.storage.getItem("Daily_Sequence_" + newSequenceName, "");
    if (!strSequenceData)
        return; // no sequence data to be loaded
 
    var sequenceData = strSequenceData.split("@NewTask");
 
    // Implementation of sequence code goes into task 0
    if (sequenceData[0].indexOf("@SequenceCode") > -1)
        this.taskList.push(new Task("SequenceCode", sequenceData[0]));
    else
        this.taskList.push(null);
 
    // Input the various tasks to be performed
    for (i=1; i < sequenceData.length; i++)
    {
        // Default taskname from a line such as "//@TaskName: Example Task Name"
        var taskname;
        var res = /@TaskName: (.+)(?=\r|\n)/.exec(sequenceData[i]);
 
        if (res)
        taskname = res[1];
        else
        taskname = "task " + i; // default taskname
 
        this.taskList.push(new Task(taskname, sequenceData[i]) );
    }
 
    // Add a final ending task which does nothing
    this.taskList.push(new Task("Done", "ShowMsg(\"Sequence Complete!\")"));
   
    }
 
    this.SaveState = function()
    {
    // Save the current state of this script (sequence name, task number, flags in use)
 
    this.storage.setItem("Daily_SequenceList", this.sequenceList.join("|") );
    this.storage.setItem("Daily_HidePageList", this.hidePageList.join("|") );
    this.storage.setItem("Daily_GlobalHide", this.globalHide);
    this.storage.setItem(this.namePlayer + "_DailyTaskNum", this.taskNum);
    this.storage.setItem(this.namePlayer + "_Daily_" + this.sequenceName + "_Flags", this.flagStr );
    this.storage.setItem(this.namePlayer + "_DailySequenceName", this.sequenceName);
    }
 
    this.LoadState = function()
    {
    // Load the previous state of this script (sequence name, task number, flags in use)
 
    this.sequenceList = this.storage.getItem("Daily_SequenceList", this.sequenceList ).split("|");
    this.hidePageList = this.storage.getItem("Daily_HidePageList", this.hidePageList ).split("|");
    this.globalHide = ('true' === this.storage.getItem("Daily_GlobalHide", 'false'));
    this.sequenceName = this.storage.getItem(this.namePlayer + "_DailySequenceName", "");
 
    if (this.sequenceName)
    {
        this.taskNum = parseInt( this.storage.getItem(this.namePlayer + "_DailyTaskNum", "0") );
        this.flagStr = this.storage.getItem(this.namePlayer + "_Daily_" + this.sequenceName + "_Flags", "|" );
    }
 
    // remove leading empty sequence strings
    //   problem only occurs intially (I think)
    if (this.sequenceList[0] === "")
    {
        this.sequenceList.splice(0,1);
        this.SaveState();
    }
 
    if (this.hidePageList[0] === "")
    {
        this.hidePageList.splice(0,1);
        this.SaveState();
    }
    }
   
    // Load the current state
    this.LoadState();
 
    // Load the sequence corresponding to the current state
    this.LoadSequence(this.sequenceName);
}
 
 
 
// task object has a description to be shown and a function that gets called
function Task(taskname, taskfun)
{
    this.taskname = taskname;
    this.taskfun = taskfun;
}
 
function Jutsu(name, code)
{
    this.name = name;
    this.code = code;
}
 
function Mission(name, jutsu, allyget, type, diff, succ)
{
    this.name = name;
    this.jutsu = jutsu;
    this.type = type;
    this.diff = diff;
    this.succ = succ;
    this.allyget = allyget;
}
 
function Quest(name, code, numsteps, flag)
{
    this.name = name;
    this.code = code;
    this.numsteps = numsteps;
    this.flag = flag;
}
 
 
////////////////////////////////////////////////////////////////////////
///////////              FLOATING WINDOWS                ///////////////
////////////////////////////////////////////////////////////////////////
 
 
function FloatingDaily()
{
 
    this.window = new Window("floatingDaily", data.storage);
   
    // Add css style for report window
            this.window.element.style.border = "2px solid #000000";
    this.window.element.style.position = "fixed";
    this.window.element.style.zIndex = "100";
    this.window.element.style.color = "#000000";
    this.window.element.style.padding = "5";
    this.window.element.style.textAlign = "left";
    this.window.element.style.overflowX = "auto";
    this.window.element.style.overflowY = "auto";
    this.window.element.style.width = "200px";
    this.window.element.style.height = "500px";
    this.window.element.style.background = "none repeat scroll 0% 0% rgb(216, 216, 216)";
       
    this.Draw = function()
    {
    // Test whether the daily window should be drawn on this page
    if (data.TestHidePage(location.href))
    {
        this.Hide();
        return;
    }
 
    // Show the window if not already
    this.window.show();
 
    // Helper array to build the HTML
    var arr = new Array();
   
    arr.push('<table width="200"><tr><td align="left">Daily Helper</td>',
         '<td align="right"><p style="margin: 0pt; text-align: right;">',
         '<a href="javascript:void(0)" id="DailyHidePageButton">',
         '(Hide)</a></p></td></tr></table><br>');
 
    arr.push("Sequence: <b>" + data.sequenceName + "</b><br><br>");
   
    arr.push('<table width="200"><tr><td align="left">',
         '<a href="javascript:void(0)" id="DailyBackTaskButton">',
         '&lt;-----</a></td>');
 
    arr.push('<td align="center"><b>' + data.taskNum + '</b> / ' + (data.taskList.length-1) + '</td>');
 
    arr.push('<td align="right"><a href="javascript:void(0)" id="DailySkipTaskButton">',
         '-----&gt;</a></td></tr></table><br>');
   
    if (data.taskNum > 0 && data.taskNum < data.taskList.length)
        arr.push('Task: <b>' + data.taskList[data.taskNum].taskname + '</b>');
    else
        arr.push('Task: Undefined Task');
 
    arr.push('<br><br><div id="DailyMsgDiv"></div><br><br>');
 
    arr.push('Your sequences:');
   
    // Generate list of sequence options to choose from
    for (var i=0; i<data.sequenceList.length; i++)
    {
        var sequenceName = data.sequenceList[i];
 
        arr.push('<p style="margin: 0pt; text-align: right;">',
             '<a href="javascript:void(0)" val="' + sequenceName,
             '" id="ChooseSequenceButton_' + sequenceName + '">',
             sequenceName + ' &gt;</a></p>');
    }
 
    arr.push('<br><p style="margin: 0pt; text-align: left;">',
         '<a href="javascript:void(0)" id="DailySequenceEditorButton">',
         '&lt; Open Sequence Editor</a></p>');
 
    arr.push('<br><p style="margin: 0pt; text-align: left;">',
         '<a href="https://docs.google.com/leaf?id=0B10D12_4U2KiNzYxZjY0YTEtYzU1Mi00Y2YzLTg5NGYtYWZlNGQzMDQyNDE0&hl=en" id="MoreSequencesButton" target="_blank">',
         '&lt; Get More Sequences</a></p>');
 
//  arr.push("<b>Flags</b>:");
//  arr.push(data.flagStr.replace(/\|/g, "<br>"));
   
    // Concatenate everything in the array to form the html
    this.window.element.innerHTML = arr.join("");
 
    // Event handlers for sequenceList items
    for (var i=0; i<data.sequenceList.length; i++)
    {
        var sequenceName = data.sequenceList[i];
 
        var elem = document.getElementById("ChooseSequenceButton_" + sequenceName);
        elem.addEventListener("click", function() { data.LoadSequence(this.id.split("_")[1]); data.SaveState(); floatingDaily.Draw(); }, false);
    }
 
    document.getElementById("DailyHidePageButton").addEventListener("click", floatingDaily.HidePage, true);
//  document.getElementById("DailyResetDataButton").addEventListener("click", floatingDaily.ResetData, true);
    document.getElementById("DailySkipTaskButton").addEventListener("click", floatingDaily.SkipTask, true);
    document.getElementById("DailyBackTaskButton").addEventListener("click", floatingDaily.BackTask, true);
    document.getElementById("DailySequenceEditorButton").addEventListener("click", floatingDaily.OpenSequenceEditor, true);
    }
 
    // Hide the window (and remove the interior)
    this.Hide = function()
    {
    this.window.element.innerHTML = "";
    this.window.hide();
    }
 
    this.showErr = function(err)
    {
    document.getElementById("DailyMsgDiv").innerHTML += err.message;
    }
 
    this.HidePage = function()
    {
    data.AddHidePage(location.href);
    floatingDaily.Hide();
    }
 
    this.ResetData = function()
    {
    data.taskNum = 1;
    data.flagStr = "|";
    data.SaveState();
    floatingDaily.Draw();
    }
 
    this.SkipTask = function()
    {
    IncrementTask();
    }
 
    this.BackTask = function()
    {
    // Temporary for debugging
    if (data.taskNum > 1)
        data.taskNum--;
    data.SaveState();
    floatingDaily.Draw();
    }
 
    this.OpenSequenceEditor = function()
    {
    // Disable hotkeys
    hotkeyLock = true;
 
    // Initialize the Editor window if not already
    floatingEditor.Draw();
 
    // Load the current sequence if it exists
    if (data.TestSequence(data.sequenceName))
        floatingEditor.LoadSequence(data.sequenceName);
 
    // Show the editor window
    floatingEditor.window.show();
    }
}
 
 
 
 
function FloatingEditor()
{
 
    this.window = new Window("floatingEditor", data.storage);
 
    // Add css style for report window
   
    this.window.element.style.border = "2px solid #000000";
    this.window.element.style.position = "fixed";
    this.window.element.style.zIndex = "100";
    this.window.element.style.color = "#000000";
    this.window.element.style.padding = "5";
    this.window.element.style.textAlign = "left";
    this.window.element.style.overflowX = "auto";
    this.window.element.style.overflowY = "auto";
    this.window.element.style.width = "600px";
    this.window.element.style.height = "620px";
    this.window.element.style.background = "none repeat scroll 0% 0% rgb(216, 216, 216)";
 
       
    this.Draw = function()
    {
    // Helper array to build the HTML
    var arr = new Array();
   
    arr.push("Sequence Editor<br><br>");
 
    // Drop down box for things in the sequence list
    arr.push('Choose an existing sequence to edit:  ');
    arr.push('<select id="sequenceSelect">');
    for (var i=0; i < data.sequenceList.length; i++)
        arr.push('<option value="' + data.sequenceList[i] + '">' + data.sequenceList[i] + '</option>');
    arr.push('</select>&nbsp&nbsp');
 
    arr.push('<a href="javascript:void(0)" id="EditorLoadSequenceButton">',
         'Load Sequence &gt;</a><br>');
 
    arr.push('<br><br>');
 
    // Textbox for sequence code
    arr.push('<textarea id="sequenceCode" rows="30" cols="76"></textarea><br>');  
 
    // Remove button
    arr.push('<p style="margin: 0pt; text-align: left;">',
         '<a href="javascript:void(0)" id="EditorDeleteSequenceButton">',
         '&lt; Delete Sequence</a></p>');
 
    // Save button
    arr.push('<p style="margin: 0pt; text-align: right;">',
         '<a href="javascript:void(0)" id="EditorSaveSequenceButton">',
         'Save Sequence &gt;</a></p><br>');
 
 
    // Concatenate everything in the array to form the html
    this.window.element.innerHTML = arr.join("");
 
    floatingEditor.LoadSequence(data.sequenceName);
    document.getElementById("EditorSaveSequenceButton").addEventListener("click", floatingEditor.SaveSequence, true);
    document.getElementById("EditorDeleteSequenceButton").addEventListener("click", floatingEditor.DeleteSequence, true);
    document.getElementById("EditorLoadSequenceButton").addEventListener("click", floatingEditor.LoadSelectedSequence, true);
 
    // Load the textarea with a sample sequence
    var sequenceStr =
        "//@SequenceName: Default\n\n\n" +
        "//------------------------------------------\n" +
        "//@NewTask\n" +
        "//@TaskName: Example Task 1\n\n" +
        "ShowMsg(\"Example task 1 only shows this message\");\n\n\n" +
        "//------------------------------------------\n" +
        "//@NewTask\n" +
        "//@TaskName: Example Task 2\n\n" +
        "ShowMsg(\"Example task 2 is almost the same\");";
 
    document.getElementById("sequenceCode").value = sequenceStr;
    }
 
    // Hide the window (and remove the interior)
    this.Hide = function()
    {
    this.window.element.innerHTML = "";
    this.window.hide();
    }
 
    this.SaveSequence = function()
    {
    // Extract the sequence string
    var sequenceCode = document.getElementById("sequenceCode").value;
    var sequenceName;
 
    // Get the sequence name and verify correct input formatting
    try
    {
        sequenceName = /@SequenceName: (.+)(?=\r|\n)/.exec(sequenceCode)[1];
    }
    catch(err)
    {
        alert("Please enter a sequence name on the first line with format:\n" +
          "//@SequenceName: Example Sequence Name");
        return;
    }
   
    // Clean the string for only alphanumeric characters and spaces
    if ( !(sequenceName === sequenceName.replace(/[^a-zA-Z 0-9]+/g,'')) )
    {
        alert("Alphanumeric characters and spaces only in sequence names");
        return;
    }
 
    data.SaveSequence(sequenceName, sequenceCode);
    data.LoadSequence(sequenceName);
 
//  floatingEditor.Draw();
//  document.getElementById("sequenceCode").value = sequenceCode;
    }
 
    this.LoadSelectedSequence = function()
    {
    var elem = document.getElementById("sequenceSelect");
    var sequenceName = elem.options[elem.selectedIndex].value;
    floatingEditor.LoadSequence(sequenceName);
    }
 
    this.DeleteSequence = function()
    {
    // Extract the sequence string
    var sequenceCode = document.getElementById("sequenceCode").value;
    var sequenceName;
 
    // Get the sequence name and verify correct input formatting
    try
    {
        sequenceName = /@SequenceName: (.+)(?=\r|\n)/.exec(sequenceCode)[1];
    }
    catch(err)
    {
        alert("Please enter a sequence name on the first line with format:\n" +
          "//@SequenceName: Example Sequence Name");
        return;
    }
   
    // Clean the string for only alphanumeric characters and spaces
    if ( !(sequenceName === sequenceName.replace(/[^a-zA-Z 0-9]+/g,'')) )
    {
        alert("Alphanumeric characters and spaces only in sequence names");
        return;
    }
 
    // Make sure the user really wants to do this
    if (!confirm("Are you sure you want to delete the sequence: \'" + sequenceName + "\'?"))
        return;
 
    // Remove the sequence and reload the editor
    data.RemoveSequence(sequenceName);
    data.LoadSequence("");
    floatingEditor.LoadSequence("");
    floatingEditor.Draw();
    floatingDaily.Draw();
    }
 
    this.LoadSequence = function(sequenceName)
    {
    // If sequenceName is null do nothing
    if (!sequenceName)
        return;
 
    // sequenceName is assumed to exist... error if not
    var sequenceStr = data.storage.getItem("Daily_Sequence_" + sequenceName, "");
 
    if (!sequenceStr)
    {
        alert("Sequence " + sequenceName + " does not exist\nThis is an error that should be reported.");
        return;
    }
 
    // Otherwise, load up the sequence text
    document.getElementById("sequenceCode").value = sequenceStr;
 
    // Also load up this sequence in browser cache
    data.LoadSequence(sequenceName);
    floatingDaily.Draw();
    }
 
    // hide the window by default
    this.window.hide();
}
 
 
 
 
 
////////////////////////////////////////////////////////////////////////
///////////             USER API FUNCTIONS               ///////////////
////////////////////////////////////////////////////////////////////////
 
//////////////// API Functions which halt task execution //////////////////
 
// Put up a message in the floating daily window
//     This function halts script execution
function ShowMsg(strMsg)
{
    if (!strMsg)
    strMsg = "";
    else
    strMsg += '<br>';
    throw new Error(strMsg);
}
 
// Submit a form with name strName and display message strMsg on success
//    This function always halts task execution
function FormSubmit(strName, strMsg)
{
    if (!strMsg)
    strMsg = 'Submitting Form: ' + strName;
 
    if (FormTest(strName))
    {
    // Success, submit the form and display the desired message
    document.forms.namedItem(strName).submit();
    ShowMsg(strMsg);
    }
    else
    {
    // Display error message for user to see
    ShowMsg('Form ' + strName + ' does not exist');
    }
}
 
// Move to the next task if possible
//    This function always halts task execution
function IncrementTask(strMsg)
{
    if (data.taskNum < data.taskList.length-1)
    data.taskNum++;
    data.SaveState();
    floatingDaily.Draw();
 
    ShowMsg(strMsg);
}
 
// Move to the next task if possible
//    This function does NOT halt task execution
function IncrementTaskNonBlock()
{
    if (data.taskNum < data.taskList.length-1)
    data.taskNum++;
    data.SaveState();
    floatingDaily.Draw();
}
 
// Move to next task if condition is true
//    This function halts task execution if condition is true
function IncrementTaskIf(cond, strMsg)
{
    if (cond)
    IncrementTask(strMsg);
}
 
// Change to new sequence (useful for chaining together separate sequences)
function ChangeSequence(strSequenceName, newTaskName)
{
    // Search for strSequenceName in sequence list
    for (var i=0; i < data.sequenceList.length; i++)
    if (data.sequenceList[i] === strSequenceName)
        {
        // Load the sequence and set the task number if desired
        data.LoadSequence(strSequenceName);
 
        if (newTaskName)
        {
        for (var j=1; j < data.taskList.length; j++)
            if (data.taskList[j].taskname === newTaskName)
            data.taskNum = j;
 
        }
 
        // Save state
        data.SaveState();
 
        // Redraw the window
        floatingDaily.Draw();
 
        // Done
        ShowMsg('Loaded Sequence: ' + strSequenceName);
    }
 
    // If code reaches this point, sequence does not exist
    ShowMsg('Sequence ' + strSequenceName + ' does not exist');
}
 
 
//////////////// API Functions which test the page for information //////////////////
 
// Test the document body HTML for the given text
function DocTest(strText)
{
    return (document.body.innerHTML.indexOf(strText) > -1);
}
 
// Test the href location for the given text
function LocationTest(strText)
{
    return (location.href.indexOf(strText) > -1);
}
 
// Test for the existence of a form with name = strName
function FormTest(strName)
{
    return (document.forms.namedItem(strName) ? true : false);
}
 
// Test whether a form input is already checked
function FormCheckTest(strFormName, strInputName, strInputValue)
{
    var strXPath = "";
 
    // Form name is optional, include it if necessary
    if (strFormName)
    strXPath += '//form[@name=\'' + strFormName + '\']';
 
    // Input name is mandatory (else we cannot find the input box...)
    strXPath += '//input[@name=\'' + strInputName + '\'';
 
    // Input value is also optional
    if (strInputValue)
    strXPath += ' and @value=\'' + strInputValue + '\'';
    strXPath += ']';
 
    var elem = document.evaluate(strXPath, document, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue;
 
    // Element does not exist
    if (!elem)
    return false;
 
    // Otherwise return checked state
    return elem.checked;
}
 
 
 
/////////////// API Functions to manipulate forms on the page //////////////
 
// Check a checkbox on a form
//    Returns false if element does not exist, or is disabled
function FormCheck(strFormName, strInputName, strInputValue)
{
    var strXPath = "";
 
    // Form name is optional, include it if necessary
    if (strFormName)
    strXPath += '//form[@name=\'' + strFormName + '\']';
 
    // Input name is mandatory (else we cannot find the input box...)
    strXPath += '//input[@name=\'' + strInputName + '\'';
 
    // Input value is also optional
    if (strInputValue)
    strXPath += ' and @value=\'' + strInputValue + '\'';
    strXPath += ']';
 
    var elem = document.evaluate(strXPath, document, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue;
 
    // Return without action if element does not exist or is disabled
    if (!elem || elem.disabled)
    return false;
 
    // Otherwise check the box and return true
    elem.checked = "checked";
    return true;
}
 
// Select an option form a drop down box
function FormSelect(strFormName, strSelectName, strOptionValue, strOptionText)
{
    // First get the select object
    var strXPath = "";
 
    // Form name is optional, include it if necessary
    if (strFormName)
    strXPath += '//form[@name=\'' + strFormName + '\']';
 
    // Input name is mandatory (else we cannot find the input box...)
    strXPath += '//select[@name=\'' + strSelectName + '\']';
 
    var elem = document.evaluate(strXPath, document, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue;
 
    // Element does not exist
    if (!elem  || elem.disabled)
    return false;
 
    // loop through options and select any if found
    for (var i=0; i<elem.options.length; i++)
    {
    if ( (!strOptionValue || elem.options[i].value == strOptionValue) &&
         (!strOptionText  || elem.options[i].text == strOptionText) )
    {
        elem.selectedIndex = i;
        return true;
    }
    }
 
    // If the code reaches this point, we did not find the option to select
    return false;
}
 
// Set .value field of an Input object
function FormSetValue(strFormName, strInputName, newValue)
{
    var strXPath = "";
 
    // Form name is optional, include it if necessary
    if (strFormName)
    strXPath += '//form[@name=\'' + strFormName + '\']';
 
    // Input name is mandatory (else we cannot find the input box...)
    strXPath += '//input[@name=\'' + strInputName + '\']';
 
    var elem = document.evaluate(strXPath, document, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue;
 
    // Return without action if element does not exist or is disabled
    if (!elem || elem.disabled)
    return false;
 
    // Otherwise check the box and return true
    elem.value = newValue;
    return true;
}
 
// Get .value field of an Input object
function FormGetValue(strFormName, strInputName)
{
    var strXPath = "";
 
    // Form name is optional, include it if necessary
    if (strFormName)
    strXPath += '//form[@name=\'' + strFormName + '\']';
 
    // Input name is mandatory (else we cannot find the input box...)
    strXPath += '//input[@name=\'' + strInputName + '\']';
 
    var elem = document.evaluate(strXPath, document, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue;
 
    // Return without action if element does not exist or is disabled
    if (!elem)
    return false;
 
    // Otherwise check the box and return true
    return elem.value;
}
 
 
 
 
////////////// API Functions which navigate around the BvS world //////////////
 
// Helper function to interact with BvS menus
function GoMenuPage(strMenuItem)
{
    var menucell = document.evaluate('//a[text()="' + strMenuItem + '"]', document, null,
                     XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue;                    
    FormSubmit(menucell.href.split(":")[1].split(".")[1], 'Moving to page: ' + strMenuItem);
};
 
// General function to go to any (or at least most) bvs page
function GoPage(strPageName)
{
    // Class to define how to get to a specific page
    //    funLoad is a function which loads the desired page (starting on the prereq page)
    //    strPrereq indicates whether we should start from a simpler page
    //    funTest is a function which determines whether we are already on the correct page
 
    function objPage(funLoad, strPrereq, funTest)
    {
    this.funLoad = funLoad;
    this.strPrereq = strPrereq;
    this.funTest = funTest;
    };
 
    // Create the list of page objects
    var pageList = {
    // Main menu items
    main:           new objPage(function(){GoMenuPage('Main');},        null, function(){return LocationTest('/bvs/pages/main.html');}   ),
    village:        new objPage(function(){GoMenuPage('Village');},     null, function(){return LocationTest('/bvs/village.html');}      ),
    partyhouse:     new objPage(function(){GoMenuPage('Party House');}, null, function(){return LocationTest('/bvs/partyhouse.html');}   ),
    shop:           new objPage(function(){GoMenuPage('Shop');},        null, function(){return LocationTest('/bvs/shop.html');}         ),
    consumables:    new objPage(function(){GoMenuPage('Consumables');}, null, function(){return LocationTest('/bvs/oneuseitems.html');}  ),
    worldkaiju:     new objPage(function(){GoMenuPage('WorldKaiju');},  null, function(){return LocationTest('/bvs/worldkaiju.html');}   ),
    missions:       new objPage(function(){GoMenuPage('Missions');},    null, function(){return LocationTest('/bvs/missionstart.html') || LocationTest('/bvs/missions');} ),
    quests:         new objPage(function(){GoMenuPage('Quests');},      null, function(){return LocationTest('/bvs/quests.html') || LocationTest('/bvs/questattempt.html') || LocationTest('/bvs/chuninexam.html');}       ),
    spar:           new objPage(function(){GoMenuPage('Spar');},        null, function(){return LocationTest('/bvs/spar.html');}         ),
    arena:          new objPage(function(){GoMenuPage('Arena');},       null, function(){return LocationTest('/bvs/arena.html');}        ),
    team:           new objPage(function(){GoMenuPage('Team');},        null, function(){return LocationTest('/bvs/team.html');}         ),
    jutsu:          new objPage(function(){GoMenuPage('Jutsu');},       null, function(){return LocationTest('/bvs/jutsu.html');}        ),
    summons:        new objPage(function(){GoMenuPage('Summons');},     null, function(){return LocationTest('/bvs/summon.html');}       ),
    bucket:         new objPage(function(){GoMenuPage('Bucket');},      null, function(){return LocationTest('/bvs/bucket.html');}       ),
 
    // Party house menu items
    ph_tipline:     new objPage(function(){GoMenuPage('Tip Line');},       'partyhouse', function(){return FormTest('tipline');} ),
    ph_juicebar:    new objPage(function(){GoMenuPage('\'Juice\' Bar');},  'partyhouse', function(){return FormTest('br');} ),
    ph_firstloser:  new objPage(function(){GoMenuPage('First Loser');},    'partyhouse', function(){return DocTest('Four times a day a new First Loser contest begins (and the old one finishes).');} ),
    ph_wheel:       new objPage(function(){GoMenuPage('Wheel');},          'partyhouse', function(){return FormTest('raf');} ),
    ph_jackpot:     new objPage(function(){GoMenuPage('Jackpot');},        'partyhouse', function(){return FormTest('ninrou');} ),
    ph_lottery:     new objPage(function(){GoMenuPage('Lottery');},        'partyhouse', function(){return FormTest('el');} ),
    ph_tsukiball:   new objPage(function(){GoMenuPage('Tsukiball');},      'partyhouse', function(){return FormTest('skib');} ),
    ph_bigboard:    new objPage(function(){GoMenuPage('Big Board');},      'partyhouse', function(){return DocTest('<b>The Big Board</b>');} ),
    ph_scratchies:  new objPage(function(){GoMenuPage('Scratchies');},     'partyhouse', function(){return FormTest('scratch') || FormTest('mainform2');} ),
    ph_darts:       new objPage(function(){GoMenuPage('Darts');},          'partyhouse', function(){return FormTest('dgame');} ),
    ph_partyroom:   new objPage(function(){GoMenuPage('Party Room');},     'partyhouse', function(){return FormTest('pr');} ),
    ph_crane:       new objPage(function(){GoMenuPage('Crane');},          'partyhouse', function(){return FormTest('cgame');} ),
    ph_over11k:     new objPage(function(){GoMenuPage('Over 11K');},       'partyhouse', function(){return FormTest('over11');} ),
    ph_snakeman:    new objPage(function(){GoMenuPage('SNAKEMAN');},       'partyhouse', function(){return FormTest('newsnake');} ),
    ph_roulette:    new objPage(function(){GoMenuPage('Roulette');},       'partyhouse', function(){return LocationTest('/bvs/partyhouse-roulette.html');} ),
    ph_mahjong:     new objPage(function(){GoMenuPage('Mahjong');},        'partyhouse', function(){return LocationTest('/bvs/partyhouse-mahjong.html');} ),
    ph_superfail:   new objPage(function(){GoMenuPage('SUPERFAIL');},      'partyhouse', function(){return LocationTest('/bvs/partyhouse-superfail.html');} ),
    ph_pigeons:     new objPage(function(){GoMenuPage('Pigeons');},        'partyhouse', function(){return LocationTest('/bvs/partyhouse-keno.html');} ),
    ph_flowerwars:  new objPage(function(){GoMenuPage('Flower Wars');},    'partyhouse', function(){return LocationTest('/bvs/partyhouse-hanafuda.html');} ),
    ph_pachinko:  new objPage(function(){GoMenuPage('Pachinko');},    'partyhouse', function(){return LocationTest('/bvs/partyhouse-pachinko.html');} ),
 
 
    // Linked from village
    breakfast:      new objPage(() => FormSubmit('dobreakfast'), 'main', () => LocationTest('/bvs/breakfast.html')),
    marketplace:    new objPage(() => FormSubmit('market'), 'village', () => LocationTest('/bvs/villagemarketplace.html')),
    tattoo:         new objPage(() => FormSubmit('tatstime'), 'village', () => LocationTest('/bvs/villagetattoo.html')),
    fields:         new objPage(function(){FormSubmit('fieldmenu');},  'village', function(){return LocationTest('/bvs/villagefields.html');}   ),
    phases:         new objPage(function(){FormSubmit('phases');},     'village', function(){return LocationTest('/bvs/villagephases.html') || LocationTest('/bvs/villager00t.html');} ),
    bingo:          new objPage(function(){FormSubmit('bbook');},      'village', function(){return LocationTest('/bvs/bingo');}   ),
    jutsuenhance:   new objPage(function(){FormSubmit('jenhance');},   'village', function(){return LocationTest('/bvs/villagejenhance.html');}   ),
    billycon:       new objPage(function(){FormSubmit('concenter');},  'village', function(){return LocationTest('/bvs/billycon-register.html');}  ),
    robofighto:     new objPage(function(){FormSubmit('robofighto');}, 'village', function(){return LocationTest('/bvs/villagerobofighto.html');}  ),
    zrewards:       new objPage(function(){FormSubmit('zrt');},        'village', function(){return LocationTest('/bvs/zombjarewards.html');}  ),
    kaiju:          new objPage(function(){FormSubmit('kat');},        'village', function(){return LocationTest('/bvs/villagemonsterfight');}   ),
    pizzawitch:     new objPage(function(){FormSubmit('pizzamenu');},  'village', function(){return LocationTest('/bvs/pizzawitch.html') || LocationTest('/bvs/pizzawitchgarage.html');}  ),
 
    // Linked from billycon
    glowslinging:   new objPage(function(){FormSubmit('glowsling');},   'billycon', function(){return LocationTest('/bvs/billycon-glowslinging.html') || LocationTest('/billycon-glowslingfight.html');}   ),
 
    // Linked from main page
    themes:         new objPage(function(){FormSubmit('theme');},   'main', function(){return LocationTest('/bvs/themesdifficulty.html');}  ),
    tinybeevault:   new objPage(function(){FormSubmit('tinybee');}, 'main', function(){return LocationTest('/bvs/tinybees.html');}  ),
    sponsoritem:    new objPage(function(){FormSubmit('sponsor');}, 'main', function(){return LocationTest('/bvs/sponsoritems.html');}  ),
    favors:         new objPage(function(){FormSubmit('sandstorm');}, 'main', function(){return LocationTest('/bvs/sandstorm.html');}  ),
    reaperblood:    new objPage(function(){FormSubmit('reaper');}, 'main',  function(){return LocationTest('/bvs/reaper.html');}  ),
    driftstyle:     new objPage(function(){FormSubmit('drifter');}, 'main', function(){return LocationTest('/bvs/drifter.html');}  ),
    avatar:         new objPage(function(){FormSubmit('avatar');}, 'main', function(){return LocationTest('/bvs/avatar.html');}  ),
 
 
    blank:          new objPage(null,null,null)
    };
 
    // Extract the page from the pageList object
    var page = pageList[strPageName];
    if (!page)
    throw new Error('Page list object: ' + strPageName + ' does not exist');
   
    // Test whether we are already on the correct page
    if (page.funTest())
    return;  // Early return, on a good page
 
    // If a prerequisite page is listed, go to it first
    if (page.strPrereq)
    GoPage(page.strPrereq);
 
    // Finally, move to the desired page
    page.funLoad();
    ShowMsg('Moving to page: ' + strPageName);
}
 
// Get a mission of a given rank
function GetMission(rank)
{
    GoPage('missions');
 
    if ( !LocationTest("/bvs/missionstart.html") )
    return;
 
    objMissionLookup = {
    D:    "d",
    genD: "gd",
    ninD: "nd",
    taiD: "td",
 
    C:    "c",
    genC: "gc",
    ninC: "nc",
    taiC: "tc",
 
    B:    "b",
    genB: "gb",
    ninB: "nb",
    taiB: "tb",
 
    A:    "a",
    genA: "ga",
    ninA: "na",
    taiA: "ta",
 
    AA:    "aa",
    genAA: "gaa",
    ninAA: "naa",
    taiAA: "taa",
 
    Reaper:    "reaper",
    genReaper: "greaper",
    ninReaper: "nreaper",
    taiReaper: "treaper",
 
    Monochrome:    "monochrome",
    genMonochrome: "gmonochrome",
    ninMonochrome: "nmonochrome",
    taiMonochrome: "tmonochrome",
 
    Outskirts:    "outskirts",
    Wasteland:    "wasteland",
    Burgerninja:  "burger",
    Pizzawitch:   "pizza",
    Witchinghour: "witch",
    S:            "s",
 
    Blank: ""
    };
   
    FormSubmit("misform" + objMissionLookup[rank], 'Getting ' + rank + ' rank mission');
}
 
// Set the r00t field keys
function SetField(key1, key2, key3)
{
    GoPage('fields');
 
    // See whether we are on the right field
    if ( key1 && key2 && key3 &&
     (!DocTest("<b>"+key1+"</b></td>") ||
      !DocTest("<b>"+key2+"</b></td>") ||
      !DocTest("<b>"+key3+"</b></td>")) )
    {
    // Go to the right field if possible
    if ( FormSelect(null, 'key_1', null, key1) &&
         FormSelect(null, 'key_2', null, key2) &&
         FormSelect(null, 'key_3', null, key3) )
    {
        FormSubmit('field', 'Changing to: ' + key1 + ' ' + key2 + ' ' + key3);
    }
    }
}
 
 
 
 
 
//////////////// API Functions for running missions //////////////////
 
// Enable megamissions -- this function might go away
function EnableMegamissions()
{
    GoPage('missions');
    IncrementTaskIf(DocTest("<i>MegaMissions Active!</i>"));
    FormSubmit('megamis', 'Enabling megamissions');
}
 
// Simple attempt for current mission with given jutsu... does not return, always throws exception
function MissionAttempt(jutsu)
{
    if (!jutsu)
    {
    jutsu = { code: -1, name: '' };
    }
 
    // Blindly attempt the mission using the given jutsu
    if (FormTest('domission'))
    FormSubmit('domission');
 
    if (FormTest('attempt'))
    {
    if (jutsu.code > -1)
        FormCheck(null, 'usejutsu', "" + jutsu.code);
    FormSubmit('attempt', 'Attempting mission with jutsu: ' + jutsu.name);
    }
 
    // Show message
    ShowMsg("Attempting mission with jutsu: " + jutsu.name);
}
 
// Complex attempt for current mission stack
//     Script defaults to given jutsu if not found in the stack
function MissionStackAttempt(stack, defaultjutsu)
{
    // Get the mission name cell
    var missioncell = document.evaluate('//div[@class="miscontainer"]/div/div/div/center/font/b', document, null,
                    XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue;
    var strMissionName = missioncell.innerHTML;
 
    // stack is an array of Mission objects
    for (var i in stack)
    if (stack[i].name == strMissionName)
        {
        if (stack[i].allyget)
        CheckAllyGet(stack[i].allyget);
        MissionAttempt(stack[i].jutsu);
    }
 
    // This point is not reached unless mission is not in the stack
 
    // Default jutsu
    MissionAttempt(defaultjutsu);
}
 
// Function to attempt a mission based on difficulty and success (before crank) in addition to name and mission type
function MissionDetailedStackAttempt(stack, defaultjutsu)
{
/*
<div class="miscontainer"><div class="misshadow"><div class="miscontent_g"><div class="miscontent2" align="left">
<div class="misstat_g">Genjutsu</div><center>
<font style="font-size: 18px;"><b>Wander the Sands</b></font><br>The Wasteland is endless..<br><b><i><font style="font-size: 16px;">Crank Level: 27</font></i></b>
<i><font style="font-size: 12px;"><br>(+27 Diff, +27 Succ, +135% Ryo)</font></i><font style="font-size: 12px;"></font><br><br>
<div class="misonestat_g"><table class="misonetable"><tbody><tr><td align="right">Difficulty 40&nbsp;&nbsp;&nbsp;Successes 36&nbsp;</td></tr></tbody></table></div>
*/
 
    // Get the mission name
    var missioncell = document.evaluate('//div[@class="miscontainer"]/div/div/div/center/font/b', document, null,
                    XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue;
    var strMissionName = missioncell.innerHTML;
 
    // Get mission type
    missioncell = document.evaluate('//div[@class="miscontainer"]/div/div/div/div', document, null,
                    XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue;
    var strMissionType = missioncell.innerHTML;
 
    // Get crank
    var numMissionCrank;
    if (DocTest('Crank Level'))
    {
    missioncell = document.evaluate('//div[@class="miscontainer"]/div/div/div/center', document, null,
                    XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue;
    numMissionCrank = parseInt(/Crank Level: (\d+)</.exec(missioncell.innerHTML)[1], 10);
    }
    else
    numMissionCrank = 0;
 
    // Get difficulty and successes
    missioncell = document.evaluate('//div[@class="miscontainer"]/div/div/div/center/div/table/tbody/tr/td', document, null,
                    XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue;
 
    // Difficulty 40&nbsp;&nbsp;&nbsp;Successes 36&nbsp;
    var numMissionDiff = parseInt(/Difficulty (\d+)&/.exec(missioncell.innerHTML)[1], 10) - numMissionCrank;
    var numMissionSucc = parseInt(/Successes (\d+)&/.exec(missioncell.innerHTML)[1], 10) - numMissionCrank;
 
    // stack is an array of Mission objects
    for (var i in stack)
    if (stack[i].name == strMissionName &&
        stack[i].type == strMissionType &&
        stack[i].diff == numMissionDiff &&
        stack[i].succ == numMissionSucc)
        {
        if (stack[i].allyget)
        CheckAllyGet(stack[i].allyget);
 
        MissionAttempt(stack[i].jutsu);
    }
 
    // This point is not reached unless mission is not in the stack
 
    // Default jutsu
    MissionAttempt(defaultjutsu);
}
 
 
 
/////////////  API Functions for doing quests ///////////////////
 
// Generic function to perform quest steps -- jutsu selection is currently broken
function DoGenericQuest(quest, endstep, jutsu)
{
    // Ensure that we are on the quest page or go there
    //    This function also continues a quest in progress
    GoPage('quests');
 
    if (FormTest('questcontinue'))
    FormSubmit('questcontinue');
 
    // safeguard against bad code for eval statement
    if (typeof(quest.code) != "number")
    ShowMsg("quest.code must be a number");
 
    // Sanity check to make sure that we are on the correct page
    if (!DocTest(quest.name))
    ShowMsg("We are on the wrong quest?!?");
 
    // Select the desired quest to start
    if (FormTest('quest' + quest.code))
    FormSubmit("quest" + quest.code);
 
    // Check termination conditions
    if ( (endstep == -1 && DocTest("--Epilogue--")) ||
     DocTest("--Part " + endstep + " of " + quest.numsteps + "--") )
    {
    if (quest.flag)
    {
        data.SetFlag(quest.flag);
        data.SaveState();
    }
 
    // Manually increment the task without halting execution
    if (data.taskNum < data.taskList.length-1)
        data.taskNum++;
    data.SaveState();
    floatingDaily.Draw();
 
    GoMenuPage('Quests');
    return;
    }
 
    // Set the requested jutsu
    if (jutsu && FormTest('attack'))
    FormCheck(null, 'usejutsu', '' + jutsu.code);
 
//  document.getElementById(jutsu.code).checked = "checked";
 
    // Blindly attempt the quest if we get this far
 
    if (FormTest('attack'))
    FormSubmit('attack');
 
    if (FormTest('goquest'))
    FormSubmit('goquest');
}
 
 
////////////// API Functions primarily for convenience / miscellaneous /////////////
 
// Generic function to set flags based on an ally we might get on this page
//    ally is an object of type Ally
function CheckAllyGet(strAllyName)
{
    // Somewhat better version of CheckStuffGet()
    if (DocTest("You got an Ally!<br><b>" + strAllyName + "!</b>") ||
    DocTest("<b>" + strAllyName + " joins you!</b>") ||
    DocTest("<b>" + strAllyName + " joins your party!</b>")  ||
    DocTest("<b>" + strAllyName + " joined your party!</b>") ||
    DocTest("You got <b>" + strAllyName + "!</b>")   )
    {
    data.SetFlag(strAllyName);
    data.SaveState();
    ShowMsg("Got ally:" + strAllyName);
    }
 
    // Ally Level 2
    if (DocTest("Level Up!<br><b>" + strAllyName + "</b> is now <b>" + strAllyName + " Lvl. 2!</b>"))
    {
    data.SetFlag(strAllyName + " Lvl. 2");
    data.SaveState();
    ShowMsg("Got " + strAllyName + " Lvl. 2");
    }
 
    // Ally Level 3
    if (DocTest("<b>" + strAllyName + " is now Lvl. 3!</b>")) // specific to stalker3 for now
    {
    data.SetFlag(strAllyName + " Lvl. 3");
    data.SaveState();
    ShowMsg("Got " + strAllyName + " Lvl. 3");
    }
}
 
// Set themes
function SetThemes(theme1, theme2, theme3, theme4)
{
    GoPage('themes');
 
    if (theme1 && !FormCheckTest('themes1', 'theme_entry_1', theme1))
    {
    FormCheck('themes1', 'theme_entry_1', theme1);
    FormSubmit('themes1');
    ShowMsg("Setting Opening Theme");
    }
   
    if (theme2 && !FormCheckTest('themes2', 'theme_entry_2', theme2))
    {
    FormCheck('themes2', 'theme_entry_2', theme2);
    FormSubmit('themes2');
    ShowMsg("Setting Battle Theme");
    }
   
    if (theme3 && !FormCheckTest('themes3', 'theme_entry_3', theme3))
    {
    FormCheck('themes3', 'theme_entry_3', theme3);
    FormSubmit('themes3');
    ShowMsg("Setting Ending Theme");
    }
   
    if (theme4 && !FormCheckTest('themes4', 'theme_entry_4', theme4))
    {
    FormCheck('themes4', 'theme_entry_4', theme4);
    FormSubmit('themes4');
    ShowMsg("Setting Overworld Theme");
    }    
}
 
 
 
// Change teams -- exact text strings for ally names required
function TeamChange(strAllyName1, strAllyName2, strAllyName3)
{
    GoPage('team');
 
    // Confirm teams without thinking
    if (FormTest('conteam'))
    FormSubmit('conteam');
 
    // Check whether the task is already complete
    var indstart = document.body.innerHTML.indexOf("<b>-Current Team-</b>");
    var ind1 = document.body.innerHTML.indexOf("<b>" + strAllyName1, indstart);
    var ind2 = document.body.innerHTML.indexOf("<b>" + strAllyName2, indstart);
    var ind3 = document.body.innerHTML.indexOf("<b>" + strAllyName3, indstart);
    var indend = document.body.innerHTML.indexOf("Save current team as Quickteam:");
 
    IncrementTaskIf(ind1 < indend && ind2 < indend && ind3 < indend);
 
    // Helper function to test each element against the ally name
    function AllyTest(strFor, strText)
    {
    return (strFor.indexOf('id_') > -1 &&
        ( strText.indexOf('<b>' + strAllyName1) > -1 ||
          strText.indexOf('<b>' + strAllyName2) > -1 ||
          strText.indexOf('<b>' + strAllyName3) > -1 ) );
    };
 
 
    // Get a snapshot for each message in the village chat
    var snapMessageList = document.evaluate("//label", document, null,
                     XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
 
    // Examine each snapshot
    for (var i=0; i<snapMessageList.snapshotLength; i++)
    {
    var snap = snapMessageList.snapshotItem(i);
 
    if (AllyTest(snap.htmlFor, snap.innerHTML))
        document.getElementById(snap.htmlFor).checked = "checked";
    }
 
    // Submit the form
    FormSubmit('maketeam');
}
 
 
 
////////////////////////////////////////////////////////////////////////
///////////                 DAILY CODE                 /////////////
////////////////////////////////////////////////////////////////////////
 
function DailyStep()
{
    // Perform a single step of daily
    try
    {
    // Execute SequenceCode if it exists, stored in task 0
    if (data.taskList[0])
        eval(data.taskList[0].taskfun);
 
    // Call the next function in the tasklist
    eval(data.taskList[data.taskNum].taskfun);
    }
    catch(err)
    {
    // Report the error or message on the floating window
    floatingDaily.showErr(err);
    }
}
 
 
 
////////////////////////////////////////////////////////////////////////
///////////                  HOTKEY CODE                   /////////////
////////////////////////////////////////////////////////////////////////
 
var hotkeyLock = false;
var keyDailyCheckRecentEscape = false;
 
function KeyCheck(event)
{
    var KeyID = event.keyCode;
 
    // Lock hotkeys if daily window is hidden
    if (data.TestHidePage(location.href))
    hotkeyLock = true;
   
    if ( (KeyID == 192 || KeyID == 61 || KeyID == 187) && !hotkeyLock) // backquote key (` - code 192) or equals (= - code 61 in FireFox & code 187 in Chrome, etc)
    {
    // Lock hotkey usage to protect from button spamming
    hotkeyLock = true;
 
    // Perform one daily step
    DailyStep();
 
    // Unlock hotkey usage
    hotkeyLock = false;
    }
 
    if (KeyID == 27) // escape key
    {
    // Close editor window if not already and unlock hotkeys
    floatingEditor.Hide();
    hotkeyLock = false;
 
    // Unhide daily window if hidden
    if (data.TestHidePage(location.href))
    {
        data.RemoveHidePage(location.href);
        floatingDaily.Draw();
    }
   
    // If escape is pressed twice in succession, reset the window positions
    if (keyDailyCheckRecentEscape)
    {
        floatingEditor.window.reset();
        floatingDaily.window.reset();
    }
    else
    {
        keyDailyCheckRecentEscape = true;
        setTimeout(function(){keyDailyCheckRecentEscape = false;}, 1000);
    }
    }
 
    if (KeyID == 37 && !hotkeyLock) // left arrow
    floatingDaily.BackTask();
 
    if (KeyID == 39 && !hotkeyLock) // right arrow
    floatingDaily.SkipTask();
}
 
document.documentElement.addEventListener("keyup", KeyCheck, true);
 
 
 
 
////////////////////////////////////////////////////////////////////////
///////////             PAGE LOAD RUNTIME                ///////////////
////////////////////////////////////////////////////////////////////////
 
 
// Initialize the jutsus object
var jutsus = {
    // Jutsu(name, code)
   
    SRS:       new Jutsu("Soul Reaper Style: Lunch, SandySword!", 499),
    MBST:      new Jutsu("Mind Body Switch Technique", 393),
    PWK:       new Jutsu("Projectile Weapons: Kunai", 373),
    Redeye:    new Jutsu("RedEye",  500),
    Clone:     new Jutsu("Clone Jutsu", 368),
    Disguise:  new Jutsu("Disguise Jutsu", 370),
    ETA:       new Jutsu("Exploding Tags: Activate", 371),
    PWS:       new Jutsu("Projectile Weapons: Shuriken", 372),
    Escape:    new Jutsu("Escape Jutsu", 374),
    FSFJ:      new Jutsu("Fire Style: Fireball Jutsu", 376),
    OI:        new Jutsu("Obsessive Insight", 466),
 
    PSPDP:     new Jutsu("Pinky Style: Pervert-Destroying Punch", 416),
    AOTNS:     new Jutsu("Attack on the Nervous System", 429),
    FOS:       new Jutsu("Flock of Seagulls", 419),
 
    EDUT:      new Jutsu("Epic Dog Urination Technique", 421),
    KICHC:     new Jutsu("Kido: I can has cheeseburger", 484),
    INT:       new Jutsu("I Need This", 497),
 
    FGTT:      new Jutsu("Flying Thunder God Technique", 448),
    BSTIS:     new Jutsu("Billy Style: This is Sparta", 444),
 
    SSFLB:     new Jutsu("Stalker Style: Freaking Laser Beams", 418),
 
    SRSIN:     new Jutsu("Soul Reaper Style: Imperishable Night", 480),
 
    Diagnosis: new Jutsu("Diagnosis", 485),
 
    Blank:     new Jutsu("", -1)
};
 
// Initialize the quests object
var quests = {
    // Quest(name, code, numsteps, flag)
    ShowWatchin: new Quest("Watchin' Your Shows", 8, 1),
 
    Blank:     new Quest("", -1, -1, "")
}
 
 
// Create global instance of daily data object
var data = new dailyData();
   
// Create the floating window objects
var floatingDaily = new FloatingDaily();
var floatingEditor = new FloatingEditor();
 
floatingDaily.Draw();