Facebook MobWars Helper

By Piotr P. Karwasz Last update Dec 23, 2008 — Installed 81,457 times. Daily Installs: 33, 54, 24, 20, 66, 37, 44, 28, 50, 51, 30, 46, 53, 31, 31, 37, 28, 58, 37, 27, 29, 36, 20, 39, 29, 42, 26, 20, 28, 35, 37, 39

There are 4 previous versions of this script.

Add Syntax Highlighting (this will take a few seconds, probably freezing your browser while it works)

// ==UserScript==
// @name           Facebook MobWars Helper
// @namespace      http://userscripts.org/users/49912
// @description    Helps and improves the playing experience of the Facebook application MobWars with automatic functions.
// @source         http://userscripts.org/scripts/show/29870
// @identifier     http://userscripts.org/scripts/source/29870.user.js
// @version        1.1
// @date           2008-12-23
// @creator        Piotr P. Karwasz
// @include        http://apps.facebook.com/mobwars/*
// @include        http://apps.new.facebook.com/mobwars/*
// ==/UserScript==
/*jsl:option explicit*/
/* $Revision: 186 $ */
var version_timestamp = 1230015843000;
/***
 * Function: Script Update Checker
 *
 * Description:
 * Script Update Checker (http://userscripts.org/scripts/show/20145)
 * written by Jarett (http://userscripts.org/users/38602).
 */
var version_scriptNum = 29870;
function updateCheck(forced){if((forced)||(parseInt(GM_getValue("lastUpdate","0"))+86400000<=(new Date().getTime()))){try{GM_xmlhttpRequest({method:"GET",url:"http://userscripts.org/scripts/review/"+version_scriptNum+"?"+new Date().getTime(),headers:{'Cache-Control':'no-cache'},onload:function(xhrResponse){GM_setValue("lastUpdate",new Date().getTime()+"");var rt=xhrResponse.responseText.replace(/&nbsp;?/gm," ").replace(/<li>/gm,"\n").replace(/<[^>]*>/gm,"");var scriptName=(/@name\s*(.*?)\s*$/m.exec(rt))[1];GM_setValue("targetScriptName",scriptName);if(parseInt(/version_timestamp\s*=\s*([0-9]+)/.exec(rt)[1])>version_timestamp){if(confirm("There is an update available for the Greasemonkey script \""+scriptName+".\"\nWould you like to go to the install page now?")){GM_openInTab("http://userscripts.org/scripts/show/"+version_scriptNum);}}else if(forced){alert("No update is available for \""+scriptName+".\"");}}});}catch(err){if(forced){alert("An error occurred while checking for updates:\n"+err);}}}}GM_registerMenuCommand(GM_getValue("targetScriptName","???")+" - Manual Update Check",function(){updateCheck(true);});updateCheck(false);

/* Errors */
var error = document.getElementById('errorTryAgain');
if (error) {
  location.reload();
}




var Page = new Object();

Page.init = function() {
  /* Now */
  Page.now = Math.floor(new Date().getTime() / 1000);
  var url = location.href;
  /* user */
  var div;
  div = document.getElementById('fb_menu_profile');
  div = div.getElementsByTagName('a')[0];
  Page.c_user = div.href.match(/id=([0-9]+)/)[1];
  var parts = url.split('/');
  /* type */
  Page.c_page = parts[4];
  /* params */
  Page.c_params = new Object();
  url = parts[5];
  if (!url) return;
  url = url.split('?');
  if (!url[1]) return;
  url = url[1];
  parts = url.split('&');
  for (var i = 0; i < parts.length; i++) {
    var param = parts[i].split('=');
    Page.c_params[param[0]] = param[1];
  }
}

Page.init();
/* vim:set tw=80 sts=2 et ft=javascript: */



var Menu = new Object();

function menu_intToDollars(num) {
  num = num || 0;
  var str = "";
  var tmp;
  while (num >= 1000) {
    var tmp = num % 1000;
    if (tmp > 99) tmp = "" + tmp;
    else if (tmp > 9) tmp = "0" + tmp;
    else tmp = "00" + tmp;
    str = "," + tmp + str;
    num = Math.floor(num / 1000);
  }
  str = "$" + num + str;
  return str;
}

Menu.init = function() {
  var menuCode = new Array();
  menuCode.push('<div class="scriptStatusHeader">-Script status-</div>');
  menuCode.push('<table style="width: 100%;"><tr><td>Cash</td><td id="cash_stat"style="color: green; text-align: right;"></td></tr>');
  menuCode.push('<tr><td>Bank account</td><td id="bank_stat" style="color: green; text-align: right;"></td></tr>');
  menuCode.push('<tr><td>City value</td><td id="city_stat" style="color: green; text-align: right;"></td></tr>');
  menuCode.push('<tr><td>Stock value</td><td id="stockpile_stat" style="color: green; text-align: right;"></td></tr>');
  menuCode.push('<tr><td colspan="2"><hr/></td></tr>');
  menuCode.push('<tr><td>Total</td><td id="total1_stat" style="text-align: right;"></td></tr>');
  menuCode.push('<tr><td colspan="2">&nbsp;</td></tr>');
  /* Income and expenses */
  menuCode.push('<tr><td>City income</td><td id="income_stat" style="color: green; text-align: right;"></td></tr>');
  menuCode.push('<tr><td>Job mean payout</td><td id="jobincome_stat" style="color: green; text-align: right;"></td></tr>');
  menuCode.push('<tr><td>Upkeep</td><td id="upkeep_stat" style="color: red; text-align: right;"></td></tr>');
  menuCode.push('<tr><td colspan="2"><hr/></td></tr>');
  menuCode.push('<tr><td>Total</td><td id="total2_stat" style="color: green; text-align: right;"></td></tr></table>');
  menuCode.push('<br/>');

  menuCode.push('<div class="scriptStatusContent"><span id="scripterror"></span>Status:<br /><span id="scriptstatus">Resting...</span><br /><span id="scripttimer"></span></div>');
  menuCode.push('<div><button type="button" id="prefs_button">Script Preferences</button></div>');

  var menu = document.createElement('div');
  menu.id = 'ScriptStatus';
  menu.innerHTML = menuCode.join('\n');
  menuCode.length = 0;

  menuCode.push("#ScriptStatus { z-index: 10; position:fixed; bottom:27px; ");
  menuCode.push("right:2px; ");
  menuCode.push("border:2px solid #6D84B4; background-color:#EEEEEE; color:#3B5998; padding:2px; font-weight:bold; width: 200px; min-height: 100px;}");
  menuCode.push("#ScriptStatus div.scriptStatusHeader { text-align:center; background: #6D84B4; color: #FFFFFF; }");
  menuCode.push("#ScriptStatus div.scriptStatusContent { border-width: 1px 1px 1px 1px; padding: 2px 2px 2px 2px; border-style: solid solid solid solid;}");

  var style = document.createElement('style');
  style.type = 'text/css';
  style.innerHTML = menuCode.join('');

  try { document.getElementsByTagName('head')[0].appendChild(style); }
  catch(e) {}
  document.body.insertBefore(menu, document.body.lastChild);

  var button = document.getElementById('prefs_button');
  button.addEventListener('click', Preferences.show(), true);
  Menu.update();
}

Menu.update = function() {
  var el = document.getElementById('cash_stat');
  var total = 0;
  el.innerHTML = menu_intToDollars(boss.cash);
  total += boss.cash || 0;
  el = document.getElementById('bank_stat');
  el.innerHTML = menu_intToDollars(boss.bank_cash);
  total += boss.bank_cash || 0;
  el = document.getElementById('city_stat');
  el.innerHTML = menu_intToDollars(boss.city_value);
  total += boss.city_value || 0;
  el = document.getElementById('stockpile_stat');
  el.innerHTML = menu_intToDollars(boss.stockpile_value);
  total += boss.stockpile_value || 0;
  el = document.getElementById('total1_stat');
  el.innerHTML = menu_intToDollars(total);
  /* Income and upkeep */
  el = document.getElementById('income_stat');
  el.innerHTML = menu_intToDollars(boss.total_income);
  el = document.getElementById('jobincome_stat');
  el.innerHTML = menu_intToDollars(boss.job_income);
  el = document.getElementById('upkeep_stat');
  el.innerHTML = menu_intToDollars(boss.total_upkeep);
  el = document.getElementById('total2_stat');
  total = boss.total_income + boss.job_income - boss.total_upkeep;
  el.innerHTML = '';
  if (total < 0) {
    el.style.color = 'red';
    el.innerHTML = '-';
  }
  total = Math.abs(total);
  el.innerHTML += menu_intToDollars(total);
}
/* vim:set tw=80 sts=2 et ft=javascript: */





/***
 * Object: Utils
 *
 * Description: contains some utilities functions.
 */
Utils = new Object();

/***
 * Method: Element.getElementsByClassName(name, node)
 *
 * Description:
 * Gets a list of elements with a give className.
 *
 * @param name        -- the classname to look for.
 * @param node        -- node on which we start the search.
 * @return array      -- an array of nodes matching the classname.
 */
if (document.getElementsByClassName) {
  /* Firefox 3: native implementation */
  Utils.getElementsByClassName = function(classname, node) {
    if (!node) node = document;
    return node.getElementsByClassName(classname);
  }
} else {
  Utils.getElementsByClassName = function(classname, node) {
    if (!node) node = document;
    var xpathExpression;
    var returnElements = new Array();
    xpathExpression = ".//*[contains(concat(' ', @class, ' '), ' " + classname + " ')]";
    var xpathResult = document.evaluate(xpathExpression, node, null, XPathResult.ANY_TYPE, null);

    while (node = xpathResult.iterateNext()) {
      returnElements.push(node);
    }
    return returnElements;
  }
}

/***
 * Function: Utils.getElementsByXPath(expression, node)
 *
 * Description:
 * Returns an array of elements obtained from evaluating the XPath expression on
 * the node.
 *
 * @param expression         -- the expression to evaluate.
 * @param node               -- context node, defaults to document.
 * @return array             -- an array of elements matching the expression
 */
Utils.getElementsByXPath = function(expression, node) {
  if (!node) node = document;
  var result = new Array();
  var xpathResult;
  xpathResult = document.evaluate(expression, node, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);

  var node;
  while (node = xpathResult.iterateNext()) {
    result.push(node);
  }

  return result;
}
/* vim:set tw=80 sts=2 et ft=javascript: */

function Boss() {
  this.name = 'Anonymous';
  this.cash = 0;
  this.health = 100;
  this.max_health = 100;
  this.energy = 10;
  this.max_energy = 10;
  this.stamina = 3;
  this.max_stamina = 3;
  this.exp = 0;
  this.level = 0;
  this.type = 'Unknown';
  this.new_level = 0;
  this.mobsters = 0;
  this.jail_delay = 0;
  this.actions = {"boss_type":{"page":"profile", "url_params":"user_id=" + Page.c_user, "message":"Checking name and type of your boss", "time":0}, "boss_attack":{"page":"boss", "message":"Checking attack parameters of your boss", "time":0}};
  this.preferences = new Object();
  this.hitlist = new Array();
  var obj = eval(GM_getValue('boss','({})'));
  for (var i in obj) {
    this[i] = obj[i];
  }
}

Boss.prototype = new Object();

Boss.prototype.save = function() {
  GM_setValue('boss', this.toSource());
}

Boss.prototype.updateData = function() {
  /* Upgrades */
  /* Version 1.0-beta */
  if (!itemlist.item_14 || !itemlist.item_14.stocktype) this.new_level = 1;
  /* Jail delay */
  boss.jail_delay = 0;
  /* Mobsters except myself */
  var header = document.getElementById('app8743457343_header');
  var str = header.getElementsByTagName('a')[0].innerHTML;
  this.mobsters = parseInt(str.match(/\d+/)[0]);
  /* Other data */
  header = document.getElementById('app8743457343_statusMenu');
  var divs = Utils.getElementsByClassName('wrap3outer', header);
  for (var i = 0; i < divs.length; i++) {
    str = divs[i].innerHTML;
    str = str.replace(/<[^>]*>/g, '');
    str = str.replace(/[, \t]/g,'');
    var re = /(Cash|Health|Energy|Stamina|Exp|Level): ?\$?([0-9]+)\/?([0-9]+)?/;
    var result = str.match(re);
    if (!result) continue;

    switch (result[1]) {
      case 'Cash':
        this.cash = parseInt(result[2]);
        break;
      case 'Health':
        this.health = parseInt(result[2]);
        this.max_health = parseInt(result[3]);
        break;
      case 'Energy':
        this.energy = parseInt(result[2]);
        this.max_energy = parseInt(result[3]);
        break;
      case 'Stamina':
        this.stamina = parseInt(result[2]);
        this.max_stamina = parseInt(result[3]);
        break;
      case 'Exp':
        this.exp = parseInt(result[2]);
        break;
      case 'Level':
        if (this.level != parseInt(result[2])) this.new_level = 1;
        this.level = parseInt(result[2]);
        break;
      default:
        alert("Unknown field: " + str);
    }
  }

  switch (Page.c_page) {
    case 'boss':
      header = document.getElementById('app8743457343_content');
      divs = header.getElementsByTagName('tr');
      this.attack_strength = parseInt(divs[1].getElementsByTagName('td')[1].innerHTML);
      this.defense_strength = parseInt(divs[2].getElementsByTagName('td')[1].innerHTML);
      break;
    case 'jail':
      header = document.getElementById('app8743457343_content');
      divs = header.getElementsByTagName('p');
      for (var i = 0; i < divs.length; i++) {
        var delay = divs[i].innerHTML.match(/jail in ([0-9.]+) hour/);
        if (delay) {
          delay = parseFloat(delay[1]) + 0.01;
          this.jail_delay = Page.now + Math.floor(3600 * delay);
          break;
        }
      }
      break;
    case 'profile':
      if (Page.c_page == 'profile' && Page.c_params.user_id == Page.c_user) {
        header = document.getElementById('app8743457343_content');
        header = header.getElementsByTagName('h1')[0].innerHTML;
        var result = header.match(/"(.*)", Level [0-9]+ (\w+)/);
        this.name = result[1];
        this.type = result[2];
      }
      break;
    case '':
      header = Utils.getElementsByXPath('//div[@id="app8743457343_content"]/div')[0];
      header = header.innerHTML;
      header = header.match(/More energy in (\d+) seconds/);
      if (header) boss.energy_time = Page.now + parseInt(header[1]);
  }
  this.save();
}

GM_registerMenuCommand("Reset MobWars' script database...",
  function() {
    GM_setValue('boss', '({})');
    GM_setValue('inventory', '({})');
    GM_setValue('itemlist', '({})');
    GM_setValue('jobs', '({})');
  });

/* vim:set tw=80 sts=2 et ft=javascript: */





function Stockitem() {
  this.type = 'stockpile';
  this.name = 'Unknown';
  this.attack = 0;
  this.defense = 0;
  this.upkeep = 0;
  this.price = 0;
}

function Cityitem() {
  this.type = 'city';
  this.name = 'Unknown';
  this.price = 0;
  this.income = 0;
  this.depends = '';
}

function Prepitem() {
  this.type = 'prep';
  this.name = 'Unknown';
  this.energy_per_unit = 0;
}

function Itemlist() {
  var obj = eval(GM_getValue('itemlist','({})'));
  for (var i in obj) {
    this[i] = obj[i];
  }
}

Itemlist.prototype = new Object();

Itemlist.prototype.save = function() {
  GM_setValue('itemlist', this.toSource());
}

Itemlist.prototype.updateData = function() {
  var pages = ['stockpile', 'jobs', 'city'];
  if (pages.indexOf(Page.c_page) != -1) {
    for (var item in this) {
      if (this[item].type != Page.c_page) continue;
      delete this[item];
    }
    var header = document.getElementById('app8743457343_content');
    var divs = Utils.getElementsByXPath('.//a[contains(@name,"item")]',header);
    var stocktype = 'weapon';
    for (var i = 0; i < divs.length; i++) {
      if (Utils.getElementsByXPath('..//a[contains(@href,"stockpile/#'+divs[i].name+'")]', divs[i]).length) continue;
      var div = Utils.getElementsByXPath('./ancestor::tr', divs[i])[0];
      var item_id = divs[i].name;
      var item;
      switch(Page.c_page) {
        case 'stockpile':
          item = new Stockitem();
          /* Bulletproof vest and Ford Crown Vic to switch between types */
          if (item_id == 'item_15') stocktype = 'armor';
          else if (item_id == 'item_21') stocktype = 'vehicle';
          item.stocktype = stocktype;
          break;
        case 'city':
          item = new Cityitem();
          break;
        case 'jobs':
          item = new Prepitem();
          break;
        default:
      }
      /* The name of the item */
      if (Page.c_page != 'jobs') item.name = div.getElementsByTagName('img')[0].title;
      else {
        var img = Utils.getElementsByXPath('.//a[contains(@href,"item")]/following-sibling::span', div)[0];
        item.name = img.previousSibling.title.match(/[0-9]+ (.*)/)[1];
      }
      /* The items data */
      var result;
      var str = div.innerHTML.replace(/\n/g,'');
      switch (Page.c_page) {
        case 'jobs':
          result = str.match(/\(\+(\d+)\).*Energy:&nbsp;(\d+)/);
          item.energy_per_unit = result[1] / result[2];
          break;
        case 'city':
          result = str.match(/Income: \$([0-9,]+).*\$([0-9,]+).*Owned: <[^<]*>(\d+)/);
          item.income = parseInt(result[1].replace(/,/g,''));
        /* Base price */
          item.price = parseInt(result[2].replace(/,/g,'')) * 10 / (parseInt(result[3]) + 10);
        /* Dependencies */
          result = str.match(/Built On: (\w+ \w+)/);
          if (result) {
            for (var j in this) {
              if (this[j].name == result[1]) {
                item.depends = j;
                break;
              }
            }
          }
          break;
        case 'stockpile':
          result = str.match(/Attack: (\d+)/);
          if (result) item.attack = parseInt(result[1]);
          result = str.match(/Defense: (\d+)/);
          if (result) item.defense = parseInt(result[1]);
          result = str.match(/upkeep.>\$([0-9,]+)/);
          if (result) item.upkeep = parseInt(result[1].replace(/,/g,''));
          result = str.match(/\$([0-9,]+)[^\$]*Owned/);
          if (result) item.price = parseInt(result[1].replace(/,/g,''));
          break;
        default:
          break;
      }
      this[item_id] = item;
    }
    this.save();
  }
}

/* vim:set tw=80 sts=2 et ft=javascript: */



Preferences = new Object();

Preferences.init = function() {
  /* The handlers for preferences */
  this.handlers = new Array();
  this.div = document.createElement('div');
  this.div.className = 'facebook-gm-pref';
  var css = ".facebook-gm-pref {display: none; position: absolute; z-index: 99; left: 200px; top: 200px; overflow: visible; background-color: white; border: 1px solid black; padding: 10px; min-width: 400px; min-height: 400px}";
  GM_addStyle(css);

  /* Setting title */
  this.div.innerHTML = '<h2>Script Preferences</h2>';

  /* Rule */
  this.rule = document.createElement('hr');
  this.rule.id = 'facebook-gm-rule';
  this.div.appendChild(this.rule);

  /* Building form */
  this.form = document.createElement('form');
  this.form.action = '';
  this.form.method = '';
  this.form.id = 'facebook-gm-pref';
  this.div.appendChild(this.form);

  /* Submit button */
  this.button = document.createElement('button');
  this.button.type = 'button';
  this.button.id = 'pref_submit';
  this.button.innerHTML = "Update preferences";
  this.form.appendChild(this.button);
  document.getElementsByTagName('body')[0].appendChild(this.div);
}

Preferences.populate = function(obj) {
  /* Preferences display */
  for (var i = 0; i < modules.length; i++) {
    if (obj[modules[i] + '_preferencesInterface']) obj[modules[i] + '_preferencesInterface'](this);
  }
  /* Preferences handlers */
  for (var i = 0; i < modules.length; i++) {
    if (obj[modules[i] + '_preferencesHandler']) this.handlers.push(obj[modules[i] + '_preferencesHandler']);
  }
  /* Add event listener */
  this.button.addEventListener('click', this.eventListener(), true);
  this.form.addEventListener('submit', this.eventListener(), true);
}

Preferences.eventListener = function() {
  var prefs = this;
  return function(ev) {
    var reload = false;
    var boss = eval(GM_getValue('boss', '({})'));
    /* Prevent submitting the form to the server */
    ev.preventDefault();
    /* Execute all handlers */
    for (var i = 0; i < prefs.handlers.length; i++) {
      if (prefs.handlers[i](prefs.form, boss)) reload = true;;
    }
    prefs.div.style.display = 'none';
    GM_setValue('boss', boss.toSource());
    if (reload) location.reload();
  }
}

Preferences.show = function() {
  var div = this.div;
  return function() {
    div.style.display = 'block';
    div.scrollIntoView(false);
  }
}

Preferences.init();
/* vim:set tw=80 sts=2 et ft=javascript: */

/* Modules */
var modules = ['inventory', 'datadisplay', 'bank', 'hospital', 'jobs'];
//var modules = ['hitlist', 'inventory', 'datadisplay', 'bank', 'hospital', 'jobs'];





Timer = new Object();

Timer.start = function(next_page, message, timer) {
  var span = document.getElementById('scriptstatus');
  span.innerHTML = message;
  span = document.getElementById('scripttimer');
  /* Convert seconds to string */
  var sec = timer;
  var str = (sec % 60) + ' s';
  sec = Math.floor(sec / 60);
  if (sec) {
    str = (sec % 60) + ' m ' + str;
    sec = Math.floor(sec / 60);
    if (sec) str = sec + ' h ' + str;
  }
  span.innerHTML = 'in ' + str;

  if (timer <= 0) {
    if (typeof next_page == 'object') {
      next_page.click();
    } else {
      location.href = next_page;
    }
  } else {
    timer--;
    setTimeout(Timer.start, 1000, next_page, message, timer);
  }
}
/* vim:set tw=80 sts=2 et ft=javascript: */


function hitlist_exec() {
  if (!boss.preferences.hitlist_active) return;
  if (boss.actions.hitlist) return;
   if (boss.stamina && boss.health >= .3 * boss.max_health) {
    var action = new Object();
    action.message = "Checking hitlist...";
    action.page = 'hitlist';
    action.func = 'hitlist_attack';
    action.params = [];
    action.time = Page.now + 5;
    boss.actions.hitlist = action;
  } else if (!boss.stamina) {
    var action = new Object();
    action.message = "Waiting for stamina regeneration...";
    action.page = 'hitlist';
    action.time = Page.now + 5 * 60;
    boss.actions.hitlist = action;
  }
}

function hitlist_attack() {
  if (Page.c_page != 'hitlist') return;
  var i;
  for (i = 0; i < document.forms.length; i++) {
    if (document.forms[i].action.match(/fight/)) {
      var amount = Utils.getElementsByXPath('../../td[position()=3]', document.forms[i])[0];
      amount = amount.innerHTML.replace(/[\$,]/g, '');
      amount = parseInt(amount);
      var name = Utils.getElementsByXPath('../../td[position()=1]/a', document.forms[i])[0];
      name = name.innerHTML;
      if (amount > 5000000 || boss.stamina == boss.max_stamina) {
        var message = document.getElementById('scriptstatus');
        if (message) message.innerHTML = "Hitting " + name + "...";
        document.forms[i].submit();
        return;
      }
    }
  }
  /* Rechecking hitlist */
  var action = new Object();
  action.message = "Checking hitlist...";
  action.page = 'hitlist';
  action.func = 'hitlist_attack';
  action.params = [];
  Timer.start('http://apps.facebook.com/mobwars/hitlist/', 'Reloading hitlist...', 5);
}

function hitlist_preferencesInterface(prefs) {
  if (boss.preferences.hitlist_active == undefined) boss.preferences.hitlist_active = false;
  var value = boss.preferences.hitlist_active;
  var option = new Array();
  option.push('<label for="hitlist_active">Automatically hunt bounties:</label><br />');
  if (value) {
    option.push('Yes <input type="radio" name="hitlist_active" value="1" checked="checked"/>');
    option.push('No <input type="radio" name="hitlist_active" value="0"/>');
  } else {
    option.push('Yes <input type="radio" name="hitlist_active" value="1"/>');
    option.push('No <input type="radio" name="hitlist_active" value="0" checked="checked"/>');
  }
  var div = document.createElement('div');
  div.innerHTML = option.join('\n');
  prefs.form.insertBefore(div, prefs.button);
}

function hitlist_preferencesHandler(form, boss) {
  var input = form.elements.namedItem('hitlist_active');
  if (boss.preferences.hitlist_active != input.checked) {
    delete boss.actions.hitlist;
    boss.preferences.hitlist_active = input.checked;
    return true;
  }
  return false;
}
/* vim:set tw=80 sts=2 et ft=javascript: */







/* Global object */
var inventory = eval(GM_getValue('inventory', '({})'));

function inventory_exec() {
  boss.city_check = boss.city_check || 0;
  boss.stockpile_check = boss.stockpile_check || 0;
  var pages = ['jobs', 'city', 'stockpile'];
  /* Update the number of items that we have */
  if (pages.indexOf(Page.c_page) != -1) {
    var header = document.getElementById('app8743457343_content');
    var divs = Utils.getElementsByXPath('.//a[contains(@name,"item")]',header);
    for (var i = 0; i < divs.length; i++) {
      var div = Utils.getElementsByXPath('./ancestor::tr',divs[i])[0];
      var result = div.innerHTML.match(/Owned: (<[^<]*>)?(\d+)/);
      if (result) inventory[divs[i].name] = parseInt(result[2]);
    }
    GM_setValue('inventory', inventory.toSource());
  }

  /* If we changed level update lists now */
  if (boss.new_level) {
    boss.city_check = 1;
    boss.stockpile_check = 1;
  }
  /* Update data */
  switch (Page.c_page) {
    case 'city':
      if (boss.city_check < Page.now) {
        itemlist.updateData();
        boss.city_check = Page.now + 3600;
      }
      var total_income = 0;
      var city_value = 0;
      for (var item in itemlist) {
        if (itemlist[item].type != 'city') continue;
        var owned = inventory[item] || 0;
        total_income += owned * itemlist[item].income;
        var price = itemlist[item].price / 2;
        while (owned) {
          if (owned >= 10) {
            city_value += (10 + owned) * price;
            owned -= 10;
          } else {
            city_value += (10 + owned) * owned * price / 10;
            owned = 0;
          }
        }
      }
      boss.total_income = total_income;
      boss.city_value = city_value;
      Menu.update();
      break;
    case 'stockpile':
      if (boss.stockpile_check < Page.now) {
        itemlist.updateData();
        boss.stockpile_check = Page.now + 3600;
      }
      var total_upkeep = 0;
      var stockpile_value = 0;
      for (var item in itemlist) {
        if (itemlist[item].type != 'stockpile') continue;
        var owned = inventory[item] || 0;
        total_upkeep += owned * itemlist[item].upkeep;
        stockpile_value += owned * itemlist[item].price / 2;
      }
      boss.total_upkeep = total_upkeep;
      boss.stockpile_value = stockpile_value;
      Menu.update();
      break;
    case 'jobs':
      /* Always update the list of prep objects. It's not too long */
      itemlist.updateData();
      break;
    default:
      break;
  }
  var action;
  /* Check the city buildings */
  /* The second condition should be always true */
  if (boss.city_check == 0 && Page.c_page != 'city') {
    action = new Object();
    action.page = 'city';
    action.message = 'Checking new buildings available...';
    action.time = 0;
    boss.actions.inventory_city = action;
  }
  if (boss.stockpile_check == 0 && Page.c_page != 'stockpile') {
    action = new Object();
    action.page = 'stockpile';
    action.message = 'Checking new weapons available...';
    action.time = 0;
    boss.actions.inventory_stockpile = action;
  }
}
/* vim:set tw=80 sts=2 et ft=javascript: */

function Job() {
  this.payout_min = 0;
  this.payout_max = 0;
  this.exp = 0;
  this.mobsters = 0;
  this.req_items = new Object();
  this.prep_items = new Object();
  this.payout_items = new Object();
}

function Joblist() {
  var obj = eval(GM_getValue('jobs','({})'));
  for (var i in obj) {
    this[i] = obj[i];
  }
}

Joblist.prototype = new Object();

Joblist.prototype.save = function() {
  GM_setValue('jobs', this.toSource());
}

Joblist.prototype.updateData = function() {
  if (Page.c_page == 'jobs') {
    for (var job in this) if (typeof this[job] == 'object') delete this[job];
    var header = document.getElementById('app8743457343_content');
    var divs = Utils.getElementsByClassName('rowData', header);
    for (var i = 0; i < divs.length; i++) {
      var div = divs[i];
      var job = new Job();
      /* Name */
      var tmp = div.getElementsByTagName('b')[0];
      job.name = tmp.innerHTML;
      /* Payout */
      tmp = div.innerHTML.match(/Payout:.*\$([0-9,]+) - \$([0-9,]+)/);
      if (tmp) {
        job.payout_min = parseInt(tmp[1].replace(/,/g,''));
        job.payout_max = parseInt(tmp[2].replace(/,/g,''));
      }
      /* Experience */
      tmp = div.innerHTML.match(/Experience: \+(\d+)/);
      if (tmp) job.exp = parseInt(tmp[1]);
      /* Mobsters */
      tmp = div.innerHTML.match(/Mobsters:\&nbsp;(\d+)/);
      if (tmp) job.mobsters = parseInt(tmp[1]);
      /* Energy */
      tmp = div.innerHTML.match(/Energy:\&nbsp;(\d+)/);
      if (tmp) job.energy = parseInt(tmp[1]);
      /* Requirements */
      var items = Utils.getElementsByXPath('.//a[contains(@href,"#item")]', div);
      for (var j = 0; j < items.length; j++) {
        /* Item */
        tmp = items[j].href;
        tmp = tmp.match(/(jobs|stockpile|city)\/#(item_\d+)/);
        var item_id = tmp[2];
        /* Quantity */
        tmp = items[j].nextSibling;
        if (tmp.tagName == 'SPAN' && (tmp = tmp.innerHTML.match(/\((use |\+)?(\d+)(%)?\)/))) {
          if (tmp[1] == 'use ') job.prep_items[item_id] = parseInt(tmp[2]);
          else if (tmp[1] == '+') job.payout_items[item_id] = parseInt(tmp[2]);
          else if (tmp[3] == '%') job.payout_items[item_id] = 1;
          else job.req_items[item_id] = parseInt(tmp[2]);
        } else {
          job.req_items[item_id] = 1;
        }
      }
      /* Id */
      tmp = Utils.getElementsByXPath('.//input[@name="jobid"]', div)[0];
      this[tmp.value] = job;
    }
    this.save();
  }
}

function jobs_exec() {
  if (boss.job_income == 'undefined') boss.job_income = 0;
  var jobs = new Joblist();
  if (Page.c_page == 'jobs') {
    jobs.updateData();
  } else if (boss.new_level) {
    var action = new Object();
    action.page = 'jobs';
    action.message = "Checking new jobs available...";
    action.time = 0;
    boss.actions.jobs_check = action;
    return;
  }
  var jobid = boss.preferences.job;
  var message;
  switch (jobid) {
    case 'none':
      boss.job_income = 0;
      Menu.update();
      return;
    case 'payout':
      jobid = jobs_selectJob(jobs, false);
      if (!jobid) message = "You can not perform any job.";
      break;
    case 'exp':
      jobid = jobs_selectJob(jobs, true);
      if (!jobid) message = "You can not perform any job.";
      break;
    default:
      /* Check if we can do this job */
      if (!jobs_canDoJob(jobs[jobid])) {
        message = "You can not perform the job '" + jobs[jobid].name + "'";
        jobid = 0;
        break;
      }
      var prepid;
      if (prepid = jobs_needPrepJob(jobs, jobid)) {
        if (!jobs_canDoJob(jobs[prepid])) {
          message = "You can not perform the preparatory job '" + jobs[prepid].name + "' for '" + jobs[jobid].name + "'";
          jobid = 0;
          break;
        } else {
          jobs_updateIncome(jobid);
          jobid = prepid;
        }
      } else jobs_updateIncome(jobid);
      break;
  }

  if (!jobid) {
    alert(message);
    delete boss.actions.jobs;
    boss.job_income = 0;
    Menu.update();
    return;
  }

  /* Add the action to the list if it's not already there */
  var action = new Object();
  action.page = 'jobs';
  action.message = "Doing " + jobs[jobid].name + "...";
  action.func = 'jobs_dojob';
  action.params = [jobid, jobs[jobid].name];
  var delay;
  boss.type == 'Insomniac' ? delay = 240 : delay = 300;
  action.time = Page.now + (jobs[jobid].energy - boss.energy) * delay;
  if (boss.energy_time) {
    action.time -= (action.time - boss.energy_time) % delay;
  }
  boss.actions.jobs = action;
}

function jobs_updateIncome(jobid) {
  if (!jobid) {
    boss.job_income = 0;
    Menu.update();
    return;
  }
  var jobratio = jobs_payRatio(jobid);
  var coeff;
  /* Energy per turn */
  switch (boss.type) {
    /* Turns of 60 minutes, energy 4 minutes */
    case 'Insomniac':
      coeff = 15;
      break;
    /* Turns of 54 minutes, energy 5 minutes */
    case 'Tycoon':
      coeff = 10.8;
      break;
    /* Turns of 60 minutes, energy 5 minutes */
    default:
      coeff = 12;
      break;
  }
  if (boss.job_income != Math.floor(coeff * jobratio)) {
    boss.job_income = Math.floor(coeff * jobratio);
    Menu.update();
  }
}

function jobs_canDoJob(job) {
  /* Check items */
  for (var item in job.req_items) {
    if (job.req_items[item] > inventory[item]) {
      GM_log("Not enough items for " + job.name + ": " + item + " missing");
      return false;
    }
  }
  /* Check mobsters */
  if (job.mobsters > boss.mobsters) {
    GM_log("Not enough mobsters for " + job.name);
    return false;
  }
  /* Check maximum energy */
  if (job.energy > boss.max_energy) {
    GM_log("Will never have enough energy for " + job.name);
    return false;
  }
  return true;
}

function jobs_needPrepJob(jobs, jobid) {
  for (var item in jobs[jobid].prep_items) {
    if (jobs[jobid].prep_items[item] > inventory[item]) {
      for (var id in jobs) {
        if (jobs[id].payout_items[item]) return id;
      }
    }
  }
  return 0;
}

function jobs_payRatio(jobid, jobs) {
  if (!jobs) jobs = new Joblist();
  var energy = jobs[jobid].energy + 0.0;
  for (var item in jobs[jobid].prep_items) {
    energy += jobs[jobid].prep_items[item] * itemlist[item].energy_per_unit;
  }
  energy = (jobs[jobid].payout_min + jobs[jobid].payout_max) / energy;
  return energy / 2;
}

function jobs_selectJob(jobs, exp) {
  var pay_ratio = 0;
  var exp_ratio = 0;
  var jobid = 0;
  var payed_job = 0;
  var tmpid;
  var energy;
  for (tmpid in jobs) {
    if (!jobs[tmpid].name) continue;
    var tmpexp_ratio = 0.0 + jobs[tmpid].exp / jobs[tmpid].energy;
    var tmppay_ratio = jobs_payRatio(tmpid, jobs);
    /* If this job is worse continue with the next */
    if (exp) {
      if ((tmpexp_ratio < exp_ratio) || (tmpexp_ratio == exp_ratio && tmppay_ratio < pay_ratio)) continue;
    } else {
       if ((tmppay_ratio < pay_ratio) || (tmppay_ratio == pay_ratio && tmpexp_ratio < exp_ratio)) continue;
    }
    /* Check if we can do this job */
    if (!jobs_canDoJob(jobs[tmpid])) continue;
    /* Do we need a prep job? */
    var prepid;
    if (prepid = jobs_needPrepJob(jobs, tmpid)) {
      /* Can we do the job? */
      if (!jobs_canDoJob(jobs[prepid])) continue;
      else jobid = prepid;
    } else jobid = tmpid;

    payed_job = tmpid;
    pay_ratio = tmppay_ratio;
    exp_ratio = tmpexp_ratio;
  }
  jobs_updateIncome(payed_job);
  return jobid;
}

function jobs_dojob(params, timer) {
    timer = timer || 5;
    /* We want to deposit the money into the bank */
    boss.next_pay = 0;
    var submit = Utils.getElementsByXPath('//input[@name="jobid" and @value="' + params[0] + '"]/../input[@type="submit"]');
    Timer.start(submit[0], "Doing " + params[1] + "...", timer);
}

function jobs_preferencesInterface(prefs) {
  if (boss.preferences.job == undefined) boss.preferences.job = 'none';
  var jobs = new Joblist();
  var div = document.createElement('div');
  div.innerHTML = '<label for="joblist">Job to perform: </label>';
  var select = document.createElement('select');
  var option;
  select.name = 'joblist';
  option = '<option value="none"';
  if (boss.preferences.job == 'none') option += ' selected="selected"';
  option += '>None</option><option value="payout"';
  if (boss.preferences.job == 'payout') option += ' selected="selected"';
  option += '>Best payout/energy</option><option value="exp"';
  if (boss.preferences.job == 'exp') option += ' selected="selected"';
  option += '>Best experience/energy</option>';
  select.innerHTML = option;
  for (var job in jobs) {
    if (jobs[job].name) {
      option = document.createElement('option');
      option.value = job;
      if (boss.preferences.job == option.value) option.selected = true;
      option.innerHTML = jobs[job].name;
      select.appendChild(option);
    }
  }
  div.appendChild(select);
  prefs.form.insertBefore(div, prefs.button);
}

function jobs_preferencesHandler(form, boss) {
  var jobid;
  var select = form.elements.namedItem('joblist');
  jobid = select.options[select.selectedIndex].value;
  if (jobid != boss.preferences.job) {
    delete boss.actions.jobs;
    boss.preferences.job = jobid;
    return true;
  }
  return false;
}
/* vim:set tw=80 sts=2 et ft=javascript: */






function bank_exec() {
  /* The multiplier compells you to put at least $10,000 the first time */
  var multiplier = 1;
  if (boss.next_pay == undefined) boss.next_pay = 0;
  if (boss.bank_cash == undefined) boss.bank_cash = 0;
  /* Value before update */
  var next_pay = boss.next_pay;
  switch (Page.c_page) {
    case 'bank':
      var balance = Utils.getElementsByClassName('myBalance')[0];
      if (balance) {
        var amount = balance.innerHTML.match(/\$([0-9,]+)/)[1];
        amount = amount.replace(/,/g, '');
        boss.bank_cash = parseInt(amount);
        Menu.update();
      }
      break;
    case 'city':
      var earnings = Utils.getElementsByClassName('earnings')[0];
      if (earnings) {
        var time = earnings.innerHTML.match(/Next paid in: (\d+) minute/)[1];
        boss.next_pay = Page.now + 60 * (parseInt(time) + 1);
      } else boss.next_pay = Page.now + 3600;
      break;
    case 'hospital':
      var header = document.getElementById('app8743457343_content');
      header = header.getElementsByTagName('span');
      if (header.length) {
        header = header[header.length - 1];
        header = header.innerHTML.replace(/[\$,]/g,'');
        header = parseInt(header);
        if (header) boss.bank_cash = header;
        Menu.update();
      }
      break;
    default:
      break;
  }

  if (!boss.bank_cash) multiplier = 10;
  if (!boss.preferences.bank_active) return;
  if (boss.actions.bank || boss.actions.bank_checkpay) return;
  if (next_pay < Page.now && boss.cash > 1000 * multiplier) {
    var action = new Object();
    action.message = "Going to the bank...";
    action.page = 'bank';
    action.func = 'bank_deposit';
    action.params = [];
    action.time = 0;
    boss.actions.bank = action;
  } else {
    var action = new Object();
    action.message = "Waiting for next payroll...";
    action.page = 'city';
    action.time = boss.next_pay;
    boss.actions.bank_checkdelay = action;
  }
}

function bank_deposit(params, timer) {
  var submit = Utils.getElementsByXPath('//input[@value="Deposit" or @value="Open Account"]')[0];
  var amount = submit.parentNode.elements.namedItem('deposit_amount');
  amount.value = boss.cash;
  Timer.start(submit, 'Depositing $' + boss.cash + ' into the bank...', 5);
}

function bank_preferencesInterface(prefs) {
  if (boss.preferences.bank_active == undefined) boss.preferences.bank_active = false;
  var value = boss.preferences.bank_active;
  var option = new Array();
  option.push('<label for="bank_active">Automatically deposit your money to the bank</label><br />');
  if (value) {
    option.push('Yes <input type="radio" name="bank_active" value="1" checked="checked"/>');
    option.push('No <input type="radio" name="bank_active" value="0"/>');
  } else {
    option.push('Yes <input type="radio" name="bank_active" value="1"/>');
    option.push('No <input type="radio" name="bank_active" value="0" checked="checked"/>');
  }
  var div = document.createElement('div');
  div.innerHTML = option.join('\n');
  prefs.form.insertBefore(div, prefs.button);
}

function bank_preferencesHandler(form, boss) {
  var input = form.elements.namedItem('bank_active');
  if (boss.preferences.bank_active != input.checked) {
    delete boss.actions.bank;
    delete boss.actions.bank_checkdelay;
    boss.preferences.bank_active = input.checked;
    return true;
  }
  return false;
}
/* vim:set tw=80 sts=2 et ft=javascript: */





function hospital_exec() {
  if (boss.heal_cost == undefined) boss.heal_cost = 0;
  /* If we cannot update the data it means there is not button and we can't
 * heal*/
  var can_heal = true;
  if (Page.c_page == 'hospital') {
    if (!hospital_updateData()) {
      delete boss.actions.hospital;
      can_heal = false;
    }
  }
  if (can_heal && boss.health * 100 < boss.max_health * boss.preferences.heal_limit && boss.bank_cash > boss.heal_cost) {
    var action = new Object();
    action.message = "Healing...";
    action.page = 'hospital';
    action.func = 'hospital_heal';
    action.params = [];
    action.time = 0;
    boss.actions.hospital = action;
  }
}

function hospital_updateData() {
  if (Page.c_page == 'hospital') {
    var el = Utils.getElementsByXPath('//input[@type="submit"]')[0];
    if (el) {
      el = el.value.replace(/,/g,'');
      el = parseInt(el.match(/\d+/)[0]);
      boss.heal_cost = el;
      return true;
    } else return false;
  }
}

function hospital_heal(params, timer) {
  var submit = Utils.getElementsByXPath('//input[@name="action" and @value="heal"]/../input[@type="submit"]')[0];
  if (submit) {
    Timer.start(submit, 'Healing for $' + boss.heal_cost + '...', 5);
  }
}

function hospital_preferencesInterface(prefs) {
  if (boss.preferences.heal_limit == undefined) boss.preferences.heal_limit = 0;
  if (boss.preferences.heal_limit >= 60) boss.preferences.heal_limit = 60;
  var value = boss.preferences.heal_limit;
  var option = new Array();
  option.push('<label for="heal_limit">Heal your boss when weaker than ');
  option.push('<input type="text" name="heal_limit" maxlength="3" size="3" ');
  option.push('value="' + value + '"/>');
  option.push('% of max health (0 to disable, 60% max)</label>');
  var div = document.createElement('div');
  div.innerHTML = option.join('\n');
  prefs.form.insertBefore(div, prefs.button);
}

function hospital_preferencesHandler(form, boss) {
  var input = form.elements.namedItem('heal_limit');
  input = parseInt(input.value);
  if (boss.preferences.heal_limit != input) {
    delete boss.actions.hospital;
    if (input >= 0 && input <= 100) {
      boss.preferences.heal_limit = input;
    }
    return true;
  }
  return false;
}
/* vim:set tw=80 sts=2 et ft=javascript: */





function datadisplay_exec() {
  switch (Page.c_page) {
    case 'city':
      datadisplay_ROR();
    case 'stockpile':
      var result = Utils.getElementsByXPath('//form[contains(@action,"do.php")]/select');
      for (var i = 0; i < result.length; i++) {
        var input = document.createElement('input');
        input.name = 'qty';
        input.type = 'text';
        input.setAttribute('maxlength', '3');
        input.setAttribute('size', '3');
        input.value = result[i].options[0].value;
        result[i].parentNode.insertBefore(input, result[i]);
        result[i].parentNode.removeChild(result[i]);
      }
      if (Page.c_page == 'city') break;
    case 'fight':
      if (!itemlist.item_14) break;
      var selection = datadisplay_getFightItems();
      var attack = datadisplay_getFightPower('attack', selection);
      var defense = datadisplay_getFightPower('defense', selection);
      var data = new Array();

      var header = document.getElementById('app8743457343_content');
      header = header.getElementsByTagName('h1')[0];
      header.innerHTML += '<br />';
      var span = document.createElement('span');
      span.className = 'earnings';
      data.push('(');
      data.push('Attack strength: ' + attack);
      data.push(', ');
      data.push('Defense strength: ' + defense);
      if (Page.c_page == 'stockpile') {
        var attack_up = datadisplay_getUpgrade(selection, 'attack');
        data.push('<br />');
        data.push('Best attack upgrade: ');
        switch (attack_up[0]) {
          case 0:
            data.push('N/A');
            break;
          case 1:
            data.push("1 " + itemlist[attack_up[1]].name + " at " + Math.floor(attack_up[2]) + "$/point");
            break;
          default:
            data.push(attack_up[0] + " " + itemlist[attack_up[1]].name + "s at " + Math.floor(attack_up[2]) + "$/point");
            break;
        }
        var defense_up = datadisplay_getUpgrade(selection, 'defense');
        data.push('<br />');
        data.push('Best defense upgrade: ');
        switch (defense_up[0]) {
          case 0:
            data.push('N/A');
            break;
          case 1:
            data.push("1 " + itemlist[defense_up[1]].name + " at " + Math.floor(defense_up[2]) + "$/point");
            break;
          default:
            data.push(defense_up[0] + " " + itemlist[defense_up[1]].name + "s at " + Math.floor(defense_up[2]) + "$/point");
            break;
        }
        var total_up = datadisplay_getUpgrade(selection, 'total');
        data.push('<br/>');
        data.push('Best attack/defense upgrade: ');
        switch (total_up[0]) {
          case 0:
            data.push('N/A');
            break;
          case 1:
            data.push("1 " + itemlist[total_up[1]].name + " at " + Math.floor(total_up[2]) + "$/point");
            break;
          default:
            data.push(total_up[0] + " " + itemlist[total_up[1]].name + "s at " + Math.floor(total_up[2]) + "$/point");
            break;
        }
        var str = datadisplay_getUnused(selection);
        if (str.length) {
          data.push('<br />');
          data.push('Unused items: ');
          data.push(str);
        }
      } else {
        var size = datadisplay_getMaxMobSize(attack);
        data.push(', ');
        data.push("Max mob size: " + size);
      }
      data.push(')');
      span.innerHTML = data.join('');
      header.appendChild(span);
    default:
      break;
  }
}

function datadisplay_getFightItems() {
  var items;
  var selection = new Object();
  var types = ['weapon', 'armor', 'vehicle'];

  for (var type = 0; type < types.length; type++) {
    var mobsters = boss.mobsters + 1;
    var items_left = true;
    items = new Object();
    for (var i in inventory) {
      if (!inventory[i] || !itemlist[i] || itemlist[i].type != 'stockpile' || itemlist[i].stocktype != types[type]) continue;
      if (!itemlist[i].attack && !itemlist[i].defense) continue;
      items[i] = inventory[i];
    }
    while (mobsters && items.toSource() != '({})') {
      var max_price = 0, item = undefined, best;
      /* Select the best equipment */
      for (item in items) {
        if (itemlist[item].price < max_price) continue;
        max_price = itemlist[item].price;
        best = item;
      }
      var qty = Math.min(mobsters, items[best]);
      selection[best] = qty;
      GM_log("Stockpile selection: " + qty + " " + itemlist[best].name );
      mobsters -= qty;
      delete items[best];
    }
  }
  return selection;
}

function datadisplay_getFightPower(str, selection) {
  var result = 0;
  if (str != 'attack' && str != 'defense') return;
  str == 'attack' ? result = boss.attack_strength * (boss.mobsters + 1): result = boss.defense_strength * (boss.mobsters + 1);
  var item;
  for (item in selection) {
    result += itemlist[item][str] * selection[item];
  }
  return result;
}

function datadisplay_getMaxMobSize(attack_strength) {
  var types = ['weapon', 'armor', 'vehicle'];
  var result = 1 + 3 * (boss.level - 1);
  GM_log("Max defense strength: " + result);
  for (var type = 0; type < types.length; type++) {
    var max_price = 0, max_defense = 0;
    for (var i in itemlist) {
        if (itemlist[i].type != 'stockpile' || itemlist[i].stocktype != types[type]) continue;
      /* Select the best equipment for price */
        if (itemlist[i].price > max_price && itemlist[i].defense != 0) {
          max_price = itemlist[i].price;
          max_defense = itemlist[i].defense;
        }
    }
    GM_log("Item with defense: " + max_defense);
    result += max_defense;
  }
  return Math.floor(attack_strength / result);
}

function datadisplay_ROR() {
  var income = boss.total_income + boss.job_income - boss.total_upkeep;
  for (var item in itemlist) {
    if (itemlist[item].type != 'city') continue;
    /* Dependencies */
    var depends = itemlist[item].depends;
    if (depends.length) {
      depends = itemlist[depends].price;
    } else {
      depends = 0;
    }
    var total_price = depends + Math.floor(itemlist[item].price * (10 + inventory[item]) / 10);
    var roi = Math.floor(100000 * itemlist[item].income / total_price);
    roi = roi / 1000;
    var td = Utils.getElementsByXPath('//a[@name="' + item + '"]/../../td[position() = 2]')[0];
    var str = '<br/>ROR: ' + roi;
    var len = str.length;
    while (len < 16) { str += '&nbsp;'; len++; }
    /* Inflaction */
    var inflaction = 100 * (11 + inventory[item]) / (10 + inventory[item]) - 100;
    var turns_inflaction = Math.ceil(inflaction / roi);
    /* Turns to purchase (without any money in the bank) */
    var turns_purchase = Math.ceil(total_price / income);
    var best_qty = 1;
    if (turns_purchase <= turns_inflaction) best_qty = 10;
    /* Buy by unit and we have enough money */
    if (best_qty == 1 && (boss.cash + boss.bank_cash) >= total_price) best_qty = Math.floor((boss.cash + boss.bank_cash) / total_price);
    /* Display best qty */
    var input = Utils.getElementsByXPath('..//select[@name="qty"]',td)[0];
    input.options[0].value = best_qty;
    /* Real turns to purchase */
    total_price = best_qty * Math.floor(itemlist[item].price * (10 + inventory[item]) / 10);
    if (itemlist[item].depends.length) {
    depends = itemlist[item].depends;
    var am;
    best_qty - inventory[depends] >= 0 ? am = best_qty - inventory[depends] : am = 0;
    total_price += am * itemlist[depends].price;
    }
    total_price -= boss.cash + boss.bank_cash;

    turns_purchase = Math.ceil(total_price / income);
    if (turns_purchase < 0) turns_purchase = 0;
    str += 'Turns left: ' + turns_purchase;
    var span = document.createElement('span');
    span.innerHTML = str;
    td.appendChild(span);
  }
}

function datadisplay_getUpgrade(selection, type) {
  if (type != 'attack' && type != 'defense' && type != 'total') throw new TypeError();
  var other;
  type == 'attack' ? other = 'defense': other = 'attack';
  var types = ['weapon', 'armor', 'vehicle'];
  var result = [0, 'item_14', Infinity];
  for (var i = 0; i < types.length; i++) {
    var left = boss.mobsters + 1;
    var power = Infinity, otherpower = Infinity, amount = 0, least;
    /* Select the least powerful weapon */
    for (var item in selection) {
      GM_log(item);
      if (!itemlist[item]) continue;
      if (itemlist[item].stocktype != types[i]) continue;
      left -= selection[item];
      if (type == 'total' && itemlist[item].attack + itemlist[item].defense < power) {
        power = itemlist[item].attack + itemlist[item].defense;
        amount = selection[item];
        least = item;
      } else if (itemlist[item][type] < power || itemlist[item][type] == power && itemlist[item][other] < otherpower) {
        power = itemlist[item][type];
        otherpower = itemlist[item][other];
        amount = selection[item];
        least = item;
      }
    }
    if (least) GM_log(type + ": Least powerful " + types[i] + " is " + itemlist[least].name);
    /* If there are mobsters that fight barehanded */
    if (left) {
      power = 0;
      amount = left;
      least = undefined;
    }
    /* Select best weapon */
    var best = undefined, ratio = Infinity;
    for (var item in itemlist) {
      if (itemlist[item].type != 'stockpile' || itemlist[item].stocktype != types[i]) continue;

      /* Look only for more expensive weapons */
      if (least && itemlist[item].price < itemlist[least].price) continue;

      var tmpratio;
      if (type == 'total') tmpratio = itemlist[item].attack + itemlist[item].defense - power;
      else tmpratio = itemlist[item][type] - power;
      if (tmpratio > 0) tmpratio = itemlist[item].price / tmpratio;
      else continue;
      if (tmpratio < ratio) {
        ratio = tmpratio;
        best = item;
      }
    }
    /* If we can not find a better weapon continue */
    if (!best) continue;
    GM_log(type + ": Selected " + types[i] + " " + itemlist[best].name + " with ratio " + ratio);
    /* If this weapon has a better ratio replace it */
    if (ratio < result[2]) {
      result[0] = amount;
      result[1] = best;
      result[2] = ratio;
    }
  }
  return result;
}

function datadisplay_getUnused(used) {
  var res = new Array();
  var amount;
  for (var item in itemlist) {
    if (itemlist[item].type != 'stockpile') continue;
    if (used[item] == undefined) used[item] = 0;
    if (amount = (inventory[item] - used[item])) {
      res.push(amount + " " + itemlist[item].name + (amount == 1? '': 's'));
    }
  }
  return res.join(', ');
}
/* vim:set tw=80 sts=2 et ft=javascript: */

/* Global variables */
var boss;
var itemlist;

(function() {
var chat = document.getElementsByTagName('iframe');
for (var i=0;i<chat.length;i++) chat[i].parentNode.removeChild(chat[i]);
var exception;

/* Create and update our boss and the stockpile */
try {
  boss = new Boss();
  itemlist = new Itemlist();
  boss.updateData();

/* Populate preferences*/
  Preferences.populate(this);
} catch(ex) { exception = ex;}

/* Init the menu */
try {
  Menu.init();
} catch(ex) {
  alert("Error while inserting the script's menu.\nLine: " + ex.lineNumber + ", error: " + ex.message);
}

if (exception) {
  var error = document.getElementById('scripterror');
  error.innerHTML = "Error in module 'base' on line " + exception.lineNumber + ": " + exception.message + "<br /><br />";
}

var captcha = Utils.getElementsByClassName('cap');
if (captcha.length) return;

for (var i = 0; i < modules.length; i++) {
  if (this[modules[i] + '_exec']) {
    try {
      now = Date.now();
      this[modules[i] + '_exec']();
      GM_log("*** MODULE " + modules[i] + " took " + (Date.now() - now) + " ms ***");
    } catch(ex) {
      var error = document.getElementById('scripterror');
      error.innerHTML = "Error in module '" + modules[i] + "' on line " + ex.lineNumber + ": " + ex.message + "<br /><br />";
    }
  }
}

var action = new Object();
var id;
action.time = Infinity;

/* Select an action */
var a = new Array();
for (var i in boss.actions) {
  a.push(i);
  if (boss.actions[i].time < action.time) {
    action = boss.actions[i];
    id = i;
  }
}

/* Remove action from queue */
if (!id) {
  boss.save();
  return;
}

var timer;
if (action.time < boss.jail_delay) action.time = boss.jail_delay;
action.time - Page.now > 5 ? timer = action.time - Page.now : timer = 5;

if (Page.c_page != action.page) {
  var url = 'http://apps.facebook.com/mobwars/' + action.page + '/';
  if (action.url_params) url += '?' + action.url_params;
  Timer.start(url, action.message, timer);
  if (!action.func) delete boss.actions[id];
} else if (action.func) {
  this[action.func](action.params, timer);
  delete boss.actions[id];
} else {
  /* Should never come here, unless there is a bug */
  var message;
  timer == 10 ? message = "Reloading page..." : message = action.message;
  var url = 'http://apps.facebook.com/mobwars/' + action.page + '/';
  if (action.url_params) url += '?' + action.url_params;
  Timer.start(url, action.message, timer);
  delete boss.actions[id];
}

boss.new_level = 0;
boss.save();
} ) ();
/* vim:set tw=80 sts=2 et ft=javascript: */