Khan Wars Edited by payam

By payamss Last update Sep 6, 2010 — Installed 433 times.

There are 1 previous version of this script.

the source is over 100KB, syntax highlighting in the browser is too slow

// ==UserScript==
// @name        Khan Wars Edited by payam
// @author      Bagatelle
// @license     Public Domain
// @uso:script  60707
// @uso:version 2.5
// @version     2.5
// @description Major revamp of Utopia Kingdoms interface, also works in KhanWars family of games.
// @include     http*://*utopiakingdoms.com*
// @include     http*://*khanwars.com*
// @include     http*://*guerrakhan.com*
// @include     http*://*lesseigneurs.fr*
// @include     http*://*khanwars.com.pt*
// @include     http*://*khanwars.es*
// @include     http*://*khanwars.cl*
// @include     http*://*deche.vn*
// @include     http*://*khanwars.ro*
// @include     http*://*khanwars.pl*
// @include     http*://*hansavaslari.com*
// @include     http*://*khanwars.it*
// @include     http*://*zarenkriege.de*
// @include     http*://*hanovete.com*
// @include     http*://*khanratnik.com*
// @include     http*://*draugas.lt*
// @include     http*://*khanwars.nl*
// @include     http*://*khanwars.no*
// @include     http*://*khanwars.se*
// @include     http*://*pravyteli.com*
// @include     http*://*khanwars.hu*
// @include     http*://*khanwars.ae*
// @include     http*://*khanwars.jp*
// @include     http*://*khanwars.ir*
// @include     http*://*lordwars.co.il*
// ==/UserScript==

// internationalisation

// the Words object is the UK version of the game text; it is used as the key
// to the translation dictionary -- therefore do not edit it
var Words = {
//// the following items must appear *exactly* as they do in-game; copy-paste
////
  "None": "Nenhum",
  "Single Infantry": "Infantaria simples",
  "Paste": "Colar",
  "Invert": "Inverter",
  "Formation": "Formação",
  "Default": "Padrão",
  // shortcut for speed-modified marches: attack, scavenge, transfer
  "A": "A",
  "S": "L",
  "T": "T",
  // additional march counters
  "Speed": "Velocidade",
  "Damage": "Dano",
  "Mystics": "Cerco",
  "Encumbrance": "Penumbra",
  "Non-Mystics": "Sem cerco",
  "Castles": "Castelo",
  "Effective Speed": "Velocidade Efetiva",
  "Server Arrival": "Server chegada",
  "Server Return": "Server Retorno",
  "Local Arrival": "Local Chegada",
  "Local Return": "Local Retorno",
  "Timing (no coins)": "Tempo (sem motivação)",
  "Cancel": "Cancelar",
  "Could not find that march type.": "Não foi possível encontrar este tipo de marcha.",
  "Cannot transfer.": "Impossível Trasferir.",
  // abbreviations for light cavalry, cavalry archer
  "LC": "CL",
  "CA": "CP",
  // merchant management
  "Save": "Salvar",
  "Delete": "Deletar",
  "Top Up": "Encher",
  "Distribute Evenly": "Distribuir Igualmente",
  "Reset": "Resetar",
  "Room Left": "Espaço sobrando",
  "Profit Ratio": "Lucro",
  "Source": "Fonte",
  // battle reports
  "Non-Junk (Res)": "Sem limpeza (Rec)",
  "Total Attacker Loss (Res/Pop)": "Perda total do ataque (Rec/Pop)",
  "Total Defender Loss (Res/Pop)": "Perda total da defesa (Rec/Pop)",
  "Estimated Total  "Guild": "Atelier",
  "Market": "Marché",
  "Training Grounds": "Forge",
  "Hospital": "Infirmerie",
  "Wall": "Mur",
  "Order": "Ordre",
  "Guard Station": "Cachette",
  "Store House": "Entrepôt",
  // resources
  "Gold": "Or",
  "Iron": "Fer",
  "Wood": "Bois",
  "Food": "Nourriture",
  "People": "Gens", // the exact word found in the alert() code in pohod.php
  // hero skills
  "Warrior": "Guerrier",
  "Cavalryman": "Soldat de cavalerie",
  "Defender": "Défenseur",
  "Archer": "Archer",
  "Scout": "Mobilité",
  "Merchant": "Finances",
  "Mystic": "Siège",

//// the rest of these are the way the script interfaces with the user
//// any meaningful translation will do

  // abbreviations for days of the week; can be blank strings if none desired
  "Mo": "lu",
  "Tu": "ma",
  "We": "me",
  "Th": "je",
  "Fr": "ve",
  "Sa": "sa",
  "Su": "di",
  // script interface--might include HTML markup
  "Convert Res": "Transformer Res",
  "Send Res": "Envoyer Res",
  "Overview": "Aperçu",
  "Clan Forum": "Forum du Clan",
  "Clan Admin": "Admin Clan",
  "VIP Search": "Recherche Avancée",
  "Release Troops": "Relaxer Soldats",
  "Server": "Serveur",
  "One-Way": "Sens Unique",
  "<i>Estimated</i> Server Arrival": "Arrivée Estimée, Serveur",
  "Total Res": "Ressources Totals",
  "Max Merchant": "Marchand Max",
  "Extra": "Extra",
  "Warning: not enough troops to paste all.": "Avis: soldats insuffisants pour coller.",
  "Default attack formation not found.": "Formation défaut non trouvé.",
  "Quick Roads": "Voiries Rapides",
  "Copy Skills": "Copier Habiletés",
  "Saved": "Sauvegardé",
  "Options": "Options",
  "Minimum Heal": "Patients Minimum",
  "Minimum Scavenge": "Nettoyage Minimum",
  "Steal Fraction": "Fraction Volée",
  "World Speed": "Vitesse du Monde",
  "ClanID": "ClanID",
  "Server Offset, Minutes": "Écart de Serveur, Minutes",
  "Maximum X": "X Maximum",
  "Maximum Y": "Y Maximum",
  "Default Attack Formation": "Formation Défaute",
  "Delete ALL Data (Map Too)": "Effacer toutes données (carte aussi)",
  "For world": "Pour monde",
  "Really! (This could take a long time.)": "Vraiment! (ça pourrait durer longtemps)",
  "Paste Formation": "Coller Formation",
  // troop selection tools
  "All": "Tout",
  "None": "Rien",
  "Single Infantry": "Infanterie Seul",
  "Paste": "Coller",
  "Invert": "Inverser",
  "Formation": "Formation",
  "Default": "Défaut",
  // shortcut for speed-modified marches: attack, scavenge, transfer
  "A": "A",
  "S": "N",
  "T": "T",
  // additional march counters
  "Speed": "Vitesse",
  "Damage": "Dégâts",
  "Mystics": "Siège",
  "Encumbrance": "Cargaison",
  "Non-Mystics": "Non-Siège",
  "Castles": "Châteaux",
  "Effective Speed": "Vitesse Effective",
  "Server Arrival": "Arrivée Serveur",
  "Server Return": "Retour Server",
  "Local Arrival": "Arrivée Locale",
  "Local Return": "Retour Local",
  "Timing (no coins)": "Minutage (sans motivation)",
  "Cancel": "Annuler",
  "Could not find that march type.": "Ce type de marche n'existe pas.",
  "Cannot transfer.": "Impossible de transférer.",
  // abbreviations for light cavalry, cavalry archer
  "LC": "CL",
  "CA": "AM",
  // merchant management
  "Save": "Sauvegarder",
  "Delete": "Effacer",
  "Top Up": "Compléter",
  "Distribute Evenly": "Distribuer Également",
  "Reset": "Remettre",
  "Room Left": "Espace Restant",
  "Profit Ratio": "Proportion de Benefice",
  "Source": "Source",
  // battle reports
  "Non-Junk (Res)": "Soldats de Valeur (Res)",
  "Total Attacker Loss (Res/Pop)": "Perte, Totale Attaquant (Res/Pop)",
  "Total Defender Loss (Res/Pop)": "Perte Totale, Défenseur (Res/Pop)",
  "Estimated Total Scavenge": "Nettoyage Total Estimé",
  "Survivor Capacity": "Capacité des Rescapés",
  "Copy Formation": "Copier Formation",
  "Copy Army": "Copier Armée",
  "Total Pop": "Pop Total",
  // battle report show stats inline
  "Inf": "Inf", // damage vs. infantry
  "Cav": "Cav", // damage vs. cavalry
  "Arch": "Arch", // damage vs. archers
  "Myst": "Sg", // damage vs. mystics
  "Atk+": "Atq+", // unit attack upgrades
  "Def+": "Déf+", // unit defense upgrades
  "Life": "Vie", // unit life
  "Lost Resources": "Ressources Perdus",
  "Lost Pop": "Pop Perdu",
  "Lost Life": "Vie Perdue",
  "Estimated Scavenge": "Nettoyage Estimaté",
  // sim
  "Min Unit": "Min Soldats",
  "Paste Unit": "Coller Soldats",
  "Add Unit": "Ajouter Soldats",
  "Max Upgrade": "Max Forge",
  "Paste Skill": "Coller Habiletés",
  "Max Skill": "Max Habiletés",
  // unit training
  "Total Resources to Train": "Resources à Entraîner",
  "Total Time to Train": "Temps Total à Entraîner",
  "Total Cancel": "Annuler Total",
  // map interface
  "Fastest": "Le Plus Vite",
  "Slowest": "Le Plus Lent",
  "Player": "Joueur",
  "Level": "Niveau",
  "Castle": "Château",
  "Capital": "Capitale",
  "Clan": "Clan",
  "Last Activity (Days)": "Dernièrement en Ligne (Jours)",
  "Last ms": "Dernière ms", // last known activity, JS time format
  "Data Grabbed (Days)": "Données Rassemblées (Jours)",
  "Grabbed ms": "Rassemblées ms", // data grabbed, JS time format
  "Generate Map Report": "Rapport du Carte",
  // spy reports
  "Message Age": "Âge du Message",
  "Copy": "Copier",
  "Delete Page": "Effacer Page",
  "Scavenge": "Nettoyage" // i.e., clean-up
};

Translation["ES"] = {
//// the following items must appear *exactly* as they do in-game; copy-paste
//// is the safest way to go
  // units
  "Pikeman": "Piquero",
  "Swordsman": "Soldado Espada",
  "Axeman": "Soldado Hacha",
  "Maceman": "Soldado Maza",
  "Ogre": "Halberd",
  "Berserker": "Caballero Teutónico",
  "Golem": "Husar",
  "Rogue": "Muyahid",
  "Quickwalker": "Mensajero veloz",
  "Light Cavalry": "Caballería ligera",
  "Heavy Cavalry": "Caballería pesada",
  "Warmage": "Tarán",
  "Arcanist": "Balista",
  "Fireslinger": "Catapulta",
  "Stormcaller": "Fundíbulo",
  "Shortbowman": "Tirador arco pequeño",
  "Longbowman": "Tirador arco grande",
  "Crossbowman": "Ballestero",
  "Cavalry Archer": "Tirador jinete",
  "Nobleman": "Hidalgo",
  "High Priest": "Sacerdote de Tangra",
  "Airweaver": "Cura",
  "Elf Lord": "Tamborero",
  "Warlord": "Comandante",
  "Grand Magus": "Torre de asedio",
  "Unit 26": "Samurai",
  "Unit 27": "KhanGuard",
  // buildings
  "Gold Mine": "Mina de oro",
  "Iron Mine": "Mina de hierro",
  "Lumber Mill": "Leñadores",
  "Farm": "Haciendas",
  "Homes": "Viviendas",
  "Barracks": "Cuartel",
  "Stables": "Cuadra",
  "Guild": "Taller",
  "Market": "Mercado",
  "Training Grounds": "Herrería",
  "Hospital": "Hospital Militar",
  "Wall": "Muralla",
  "Order": "Orden",
  "Guard Station": "Refugio",
  "Store House": "Almacenes",
  // resources
  "Gold": "Oro",
  "Iron": "Hierro",
  "Wood": "Madera",
  "Food": "Comida",
  "People": "Gente", // the exact word found in the alert() code in pohod.php
  // hero skills
  "Warrior": "Guerrero",
  "Cavalryman": "Caballero",
  "Defender": "Defensor",
  "Archer": "Tirador",
  "Scout": "Explorador",
  "Merchant": "Finanzas",
  "Mystic": "Asedio",

//// the rest of these are the way the script interfaces with the user
//// any meaningful translation will do
  // abbreviations for days of the week; can be blank strings if none desired
  "Mo": "Lu",
  "Tu": "Ma",
  "We": "Mi",
  "Th": "Ju",
  "Fr": "Vi",
  "Sa": "Sa",
  "Su": "Do",
  // script interface—might include HTML markup
  "Convert Res": "Convertir Recursos",
  "Send Res": "Enviar Recursos",
  "Overview": "Info. General",
  "Clan Forum": "Foro del Clan",
  "Clan Admin": "Administrar Clan",
  "VIP Search": "Búsqueda VIP",
  "Release Troops": "Actualizar Ejercito",
  "Server": "Servidor",
  "One-Way": "Direccion Unica",
  "Estimated Server Arrival": "Hora Serv. Estimada Llegada",
  "Total Res": "Recursos Totales",
  "Max Merchant": "Máximo Comerciable",
  "Extra": "Sobrante",
  "Warning: not enough troops to paste all.": "Advertencia: cantidad de tropas insuficientes.",
  "Default attack formation not found.": "Formacion de ataque predeterminada no encontrada.",
  "Quick Roads": "Caminos Rápidos",
  "Copy Skills": "Copiar Habilidades",
  "Saved": "Guardado",
  "Options": "Opciones",
  "Minimum Heal": "Curación Mínima",
  "Minimum Scavenge": "Limpieza Mínima",
  "Steal Fraction": "Fracción Robada",
  "World Speed": "Velocidad del Mundo",
  "ClanID": "ID del Clan",
  "Server Offset, Minutes": "Desincronización Servidor, minutos",
  "Maximum X": "X Máxima",
  "Maximum Y": "Y Máxima",
  "Default Attack Formation": "Formación de Ataque Predeterminada",
  "Delete ALL Data (Map Too)": "Borrar TODOS los Datos (también mapa)",
  "For world": "Para el Mundo",
  "Really! (This could take a long time.)": "En Serio! (puede tardar mucho)",
  "Paste Formation": "Pegar Formación",
  // troop selection tools
  "All": "Todo",
  "None": "Ninguno",
  "Single Infantry": "Solo 1 Infanteria",
  "Paste": "Pegar",
  "Invert": "Invertir",
  "Formation": "Formación",
  "Default": "Predeterminado",
  // shortcut for speed-modified marches: attack, scavenge, transfer
  "A": "A",
  "S": "L",
  "T": "T",
  // additional march counters
  "Speed": "Velocidad",
  "Damage": "Daño",
  "Mystics": "Asedio",
  "Encumbrance": "Carga",
  "Non-Mystics": "Sin-Asedio",
  "Castles": "Castillos",
  "Effective Speed": "Velocidad Efectiva",
  "Server Arrival": "LLegada hs Servidor",
  "Server Return": "Vuelta hs Servidor",
  "Local Arrival": "LLegada hs Local",
  "Local Return": "Vuelta hs Local",
  "Timing (no coins)": "Tiempo (sin motivacion)",
  "Cancel": "Cancelar",
  "Could not find that march type.": "No se encuentra ese tipo de marcha.",
  "Cannot transfer.": "No se puede transferir.",
  // abbreviations for light cavalry, cavalry archer
  "LC": "CL",
  "CA": "TJ",
  // merchant management
  "Save": "Guardar",
  "Delete": "Borrar",
  "Top Up": "Todo",
  "Distribute Evenly": "Distrib. Equitativa",
  "Reset": "Reiniciar",
  "Room Left": "Espacio Sobrante",
  "Profit Ratio": "Ratio de Beneficio",
  "Source": "Fuente",
  // battle reports
  "Non-Junk (Res)": "Sin Limpieza (Rec)",
  "Total Attacker Loss (Res/Pop)": "Pérdida Total de Ataque (Rec/Pob)",
  "Total Defender Loss (Res/Pop)": "Pérdida Total de Defensa (Rec/Pob)",
  "Estimated Total Scavenge": "Limpieza Total Estimada",
  "Survivor Capacity": "Capacidad de Sobrevivir",
  "Copy Formation": "Copiar Formación",
  "Copy Army": "Copiar Ejercito",
  "Total Pop": "Pob. Total",
  // battle report show stats inline
  "Inf": "Inf", // damage vs. infantry
  "Cav": "Cab", // damage vs. cavalry
  "Arch": "Arq", // damage vs. archers
  "Myst": "Ase", // damage vs. mystics
  "Atk+": "Atq+", // unit attack upgrades
  "Def+": "Def+", // unit defense upgrades
  "Life": "Vida", // unit life
  "Lost Resources": "Recursos Perdidos",
  "Lost Pop": "Pob. Perdida",
  "Lost Life": "Vida Perdida",
  "Estimated Scavenge": "Limpieza Estimada",
  // sim
  "Min Unit": "Min Unid",
  "Paste Unit": "Pegar Unid",
  "Add Unit": "Agreg Unid",
  "Max Upgrade": "Mejora Max",
  "Paste Skill": "Pegar Habilidad",
  "Max Skill": "Max Habilidades",
  // unit training
  "Total Resources to Train": "Recursos Totales para Entrenar",
  "Total Time to Train": "Tiempo Total para Entrenar",
  "Total Cancel": "Cancelarlo Todo",
  // map interface
  "Fastest": "Rapidísimo",
  "Slowest": "Lentísimo",
  "Player": "Jugador",
  "Level": "Nivel",
  "Castle": "Castillo",
  "Capital": "Capital",
  "Clan": "Clan",
  "Last Activity (Days)": "Ultima Actividad (Dias)",
  "Last ms": "Ultimo Act ms", // last known activity, JS time format
  "Data Grabbed (Days)": "Datos Guardados (Dias)",
  "Grabbed ms": "Guardado ms", // data grabbed, JS time format
  "Generate Map Report": "Generar Reporte de Mapa",
  // spy reports
  "Message Age": "Fecha del Mensaje",
  "Copy": "Copiar",
  "Delete Page": "Borrar Pagina",
  "Scavenge": "Limpieza" // i.e., clean-up
};

Translation["CL"] = {
//// the following items must appear *exactly* as they do in-game; copy-paste
//// is the safest way to go
  // units
  "Pikeman": "Piquero",
  "Swordsman": "Soldado Espada",
  "Axeman": "Soldado Hacha",
  "Maceman": "Soldado Maza",
  "Ogre": "Halberd",
  "Berserker": "Caballero Teutónico",
  "Golem": "Husar",
  "Rogue": "Muyahid",
  "Quickwalker": "Mensajero veloz",
  "Light Cavalry": "Caballería ligera",
  "Heavy Cavalry": "Caballería pesada",
  "Warmage": "Tarán",
  "Arcanist": "Balista",
  "Fireslinger": "Catapulta",
  "Stormcaller": "Fundíbulo",
  "Shortbowman": "Tirador arco pequeño",
  "Longbowman": "Tirador arco grande",
  "Crossbowman": "Ballestero",
  "Cavalry Archer": "Tirador jinete",
  "Nobleman": "Hidalgo",
  "High Priest": "Sacerdote de Tangra",
  "Airweaver": "Cura",
  "Elf Lord": "Tamborero",
  "Warlord": "Comandante",
  "Grand Magus": "Torre de asedio",
  "Unit 26": "Samurai",
  "Unit 27": "KhanGuard",
  // buildings
  "Gold Mine": "Mina de oro",
  "Iron Mine": "Mina de hierro",
  "Lumber Mill": "Leñadores",
  "Farm": "Haciendas",
  "Homes": "Viviendas",
  "Barracks": "Cuartel",
  "Stables": "Cuadra",
  "Guild": "Taller",
  "Market": "Mercado",
  "Training Grounds": "Herrería",
  "Hospital": "Hospital Militar",
  "Wall": "Muralla",
  "Order": "Orden",
  "Guard Station": "Refugio",
  "Store House": "Almácenes",
  // resources
  "Gold": "Oro",
  "Iron": "Hierro",
  "Wood": "Madera",
  "Food": "Comida",
  "People": "Gente", // the exact word found in the alert() code in pohod.php
  // hero skills
  "Warrior": "Guerrero",
  "Cavalryman": "Caballero",
  "Defender": "Defensor",
  "Archer": "Tirador",
  "Scout": "Explorador",
  "Merchant": "Finanzas",
  "Mystic": "Asedio",

//// the rest of these are the way the script interfaces with the user
//// any meaningful translation will do
  // abbreviations for days of the week; can be blank strings if none desired
  "Mo": "Lu",
  "Tu": "Ma",
  "We": "Mi",
  "Th": "Ju",
  "Fr": "Vi",
  "Sa": "Sa",
  "Su": "Do",
  // script interface—might include HTML markup
  "Convert Res": "Convertir Recursos",
  "Send Res": "Enviar Recursos",
  "Overview": "Info. General",
  "Clan Forum": "Foro del Clan",
  "Clan Admin": "Administrar el Clan",
  "VIP Search": "Busqueda VIP",
  "Release Troops": "Actualizar Ejercito",
  "Server": "Servidor",
  "One-Way": "Direccion Unica",
  "Estimated Server Arrival": "Estimado Llegada hs Servidor",
  "Total Res": "Recursos Totales",
  "Max Merchant": " Cantidad Max Mercado",
  "Extra": "Sobrante",
  "Warning: not enough troops to paste all.": "Cuidado: cantidad de tropas insuficientes.",
  "Default attack formation not found.": "Formacion de ataque predeterminada no encontrada.",
  "Quick Roads": "Caminos Rapidos",
  "Copy Skills": "Copiar Habilidades",
  "Saved": "Guardado",
  "Options": "Opciones",
  "Minimum Heal": "Curacion Minima",
  "Minimum Scavenge": "Limpieza Minima",
  "Steal Fraction": "Fraccion de Robo",
  "World Speed": "Velocidad Mundo",
  "ClanID": "ClanID",
  "Server Offset, Minutes": "Servidor fuera de juego, Minutos",
  "Maximum X": "Maximo X",
  "Maximum Y": "Maximo Y",
  "Default Attack Formation": "Formacion de Ataque Predeterminada",
  "Delete ALL Data (Map Too)": "Borrar Todos los Datos (Mapa Tambien)",
  "For world": "Para el Mundo",
  "Really! (This could take a long time.)": "En Serio! (Esto tomara mucho tiempo.)",
  "Paste Formation": "Pegar Formacion",
  // troop selection tools
  "All": "Todo",
  "None": "Ninguno",
  "Single Infantry": "Solo 1 Infanteria",
  "Paste": "Pegar",
  "Invert": "Invertir",
  "Formation": "Formacion",
  "Default": "Predeterminado",
  // shortcut for speed-modified marches: attack, scavenge, transfer
  "A": "A",
  "S": "L",
  "T": "T",
  // additional march counters
  "Speed": "Velocidad",
  "Damage": "Daño",
  "Mystics": "Asedio",
  "Encumbrance": "Penumbra",
  "Non-Mystics": "Sin-Asedio",
  "Castles": "Castillos",
  "Effective Speed": "Velocidad Efectiva",
  "Server Arrival": "LLegada hs Servidor",
  "Server Return": "Vuelta hs Servidor",
  "Local Arrival": "LLegada hs Local",
  "Local Return": "Vuelta hs Local",
  "Timing (no coins)": "Tiempo (sin motivacion)",
  "Cancel": "Cancelar",
  "Could not find that march type.": "No se encuentra ese tipo de marcha.",
  "Cannot transfer.": "No se puede transferir.",
  // abbreviations for light cavalry, cavalry archer
  "LC": "CL",
  "CA": "TJ",
  // merchant management
  "Save": "Guardar",
  "Delete": "Borrar",
  "Top Up": "Todo",
  "Distribute Evenly": "Distrib. Equitativa",
  "Reset": "Reiniciar",
  "Room Left": "Espacio Sobrante",
  "Profit Ratio": "Lucro",
  "Source": "Fuente",
  // battle reports
  "Non-Junk (Res)": "Sin Limpieza (Rec)",
  "Total Attacker Loss (Res/Pop)": "Perdida Total de Ataque (Rec/Pob)",
  "Total Defender Loss (Res/Pop)": "Perdida Total de Defensa (Rec/Pob)",
  "Estimated Total Scavenge": "Limpieza Total Estimada",
  "Survivor Capacity": "Capacidad de Sobrevivir",
  "Copy Formation": "Copiar Formacion",
  "Copy Army": "Copiar Ejercito",
  "Total Pop": "Pob Total",
  // battle report show stats inline
  "Inf": "Inf", // damage vs. infantry
  "Cav": "Cab", // damage vs. cavalry
  "Arch": "Arq", // damage vs. archers
  "Myst": "Ase", // damage vs. mystics
  "Atk+": "Atq+", // unit attack upgrades
  "Def+": "Def+", // unit defense upgrades
  "Life": "Vida", // unit life
  "Lost Resources": "Recursos Perdidos",
  "Lost Pop": "Pobla. Perdida",
  "Lost Life": "Vida Perdida",
  "Estimated Scavenge": "Limpieza Estimada",
  // sim
  "Min Unit": "Min Unid",
  "Paste Unit": "Pegar Unid",
  "Add Unit": "Agreg Unid",
  "Max Upgrade": "Max Mejoras",
  "Paste Skill": "Pegar Habilidades.",
  "Max Skill": "Max Habilidades",
  // unit training
  "Total Resources to Train": "Recursos Totales para Entrenar",
  "Total Time to Train": "Tiempo Total para Entrenar",
  "Total Cancel": "Cancelar Todo",
  // map interface
  "Fastest": "Rapidisimo",
  "Slowest": "Lentisimo",
  "Player": "Jugador",
  "Level": "Nivel",
  "Castle": "Castillo",
  "Capital": "Capital",
  "Clan": "Clan",
  "Last Activity (Days)": "Ultima Actividad (Dias)",
  "Last ms": "Ultimo Act ms", // last known activity, JS time format
  "Data Grabbed (Days)": "Datos Guardados(Dias)",
  "Grabbed ms": "Dat Guard ms", // data grabbed, JS time format
  "Generate Map Report": "Generar Reporte de Mapa",
  // spy reports
  "Message Age": "Tiempo del Mensaje",
  "Copy": "Copiar",
  "Delete Page": "Borrar Pagina",
  "Scavenge": "Limpieza" // i.e., clean-up
};

Translation["VN"] = {
  "Pikeman": "Giáo binh",
  "Swordsman": "Kiếm sĩ",
  "Axeman": "Phủ quân",
  "Maceman": "Chùy quân",
  "Ogre": "Phủ bổ đầu",
  "Berserker": "Hiệp sĩ Teuton",
  "Golem": "Quân Huskarle",
  "Rogue": "Quân Janissary",
  "Quickwalker": "Thiên lý mã",
  "Light Cavalry": "Kỵ binh",
  "Heavy Cavalry": "Kỵ sĩ",
  "Warmage": "Thiết chiến xa",
  "Arcanist": "Tiễn xa",
  "Fireslinger": "Thạch xa",
  "Stormcaller": "Đẩu Thạch Cơ",
  "Shortbowman": "Cung thủ",
  "Longbowman": "Trường cung thủ",
  "Crossbowman": "Thập tự cung",
  "Cavalry Archer": "Xạ Kỵ Binh",
  "Nobleman": "Quý tộc",
  "High Priest": "Mục sư Tangra",
  "Airweaver": "Tu sĩ",
  "Elf Lord": "Cổ thủ quân",
  "Warlord": "Chiến kỵ quân",
  "Grand Magus": "Tháp chiến xa",
  "Unit 26": "Samurai",
  "Unit 27": "KhanGuard",
  "Gold Mine": "Mỏ Vàng",
  "Iron Mine": "Mỏ Sắt",
  "Lumber Mill": "Nhà Đốn Gỗ",
  "Farm": "Nông Trại",
  "Homes": "Nhà ở",
  "Barracks": "Trại lính",
  "Stables": "Trại ngựa",
  "Guild": "Xưởng Cơ giới",
  "Market": "Khu Chợ",
  "Training Grounds": "Lò rèn",
  "Hospital": "Bệnh xá",
  "Wall": "Tường thành",
  "Order": "Thánh điện",
  "Guard Station": "Hầm chứa",
  "Store House": "Kho hàng",
  "Warrior": "Chiến binh",
  "Cavalryman": "Kỵ Binh",
  "Defender": "Phòng thủ",
  "Archer": "Xạ tiễn",
  "Scout": "Hành quân",
  "Merchant": "Kinh tế",
  "Mystic": "Cơ giới",
  "Gold": "Vàng",
  "Iron": "Sắt",
  "Wood": "Gỗ",
  "Food": "Thịt",
  "People": "Quân số"
}

Translation["RO"] = {
  "Pikeman": "Purtător de lance",
  "Swordsman": "Purtător de spadă",
  "Axeman": "Luptător cu secure",
  "Maceman": "Luptător cu buzdugan",
  "Ogre": "Halebardist",
  "Berserker": "Cavaler teuton",
  "Golem": "Huscarl",
  "Rogue": "Ienicer",
  "Quickwalker": "Spion",
  "Light Cavalry": "Cavalerie uşoară",
  "Heavy Cavalry": "Cavalerie grea",
  "Warmage": "Berbeci",
  "Arcanist": "Balistă",
  "Fireslinger": "Catapultă",
  "Stormcaller": "Trebuchet",
  "Shortbowman": "Arcaş cu arc mic",
  "Longbowman": "Arcaş cu arc mare",
  "Crossbowman": "Arbaletist",
  "Cavalry Archer": "Călăreţ arcaş",
  "Nobleman": "Nobil",
  "High Priest": "Călugăr Războinic",
  "Airweaver": "Călugăr",
  "Elf Lord": "Toboşar",
  "Warlord": "Lord de război",
  "Grand Magus": "Turn de asediu",
  "Unit 26": "Samurai",
  "Unit 27": "Garda Khanului",
  "Gold Mine": "Mină de aur",
  "Iron Mine": "Mină de fier",
  "Lumber Mill": "Tăietori de lemne",
  "Farm": "Ferme",
  "Homes": "Locuinţe",
  "Barracks": "Unitate militară",
  "Stables": "Grajduri",
  "Guild": "Atelier",
  "Market": "Piaţă",
  "Training Grounds": "Fierărie",
  "Hospital": "Infirmerie",
  "Wall": "Perete",
  "Order": "Ordin",
  "Guard Station": "Ascunzătoare",
  "Store House": "Depozit",
  "Warrior": "Războinic",
  "Cavalryman": "Cavalerie",
  "Defender": "Apărător",
  "Archer": "Arcas",
  "Scout": "Explorator",
  "Merchant": "Finanţe",
  "Mystic": "Asediu",
  "Gold": "Aur",
  "Iron": "Fier",
  "Wood": "Lemne",
  "Food": "Hrană",
  "People": "Oameni"
}

Translation["PL"] = {
  "Pikeman": "Pikinier",
  "Swordsman": "Szermierz",
  "Axeman": "Topornik",
  "Maceman": "Pałkarz",
  "Ogre": "Opancerzony topornik",
  "Berserker": "Teutoński rycerz",
  "Golem": "Goplanin",
  "Rogue": "Janczar",
  "Quickwalker": "Szpieg",
  "Light Cavalry": "Lekka kawaleria",
  "Heavy Cavalry": "Ciężka kawaleria",
  "Warmage": "Taran",
  "Arcanist": "Balista",
  "Fireslinger": "Katapulta",
  "Stormcaller": "Maszyna oblężnicza",
  "Shortbowman": "Łucznik",
  "Longbowman": "Zawodowy łucznik",
  "Crossbowman": "Kusznik",
  "Cavalry Archer": "Konny łucznik",
  "Nobleman": "Szlachcic",
  "High Priest": "Kapłan Tangry",
  "Airweaver": "Mnich",
  "Elf Lord": "Dobosz",
  "Warlord": "Warlord",
  "Grand Magus": "Wieża oblężnicza",
  "Unit 26": "Samuraj",
  "Unit 27": "Straż Czyngis-Chana",
  "Gold Mine": "Kopalnia złota",
  "Iron Mine": "Kopalnia żelaza",
  "Lumber Mill": "Tartak",
  "Farm": "Farmy",
  "Homes": "Mieszkania",
  "Barracks": "Koszary",
  "Stables": "Stajnie",
  "Guild": "Warsztat",
  "Market": "Rynek",
  "Training Grounds": "Kuźnia",
  "Hospital": "Szpital",
  "Wall": "Mur",
  "Order": "Zakon",
  "Guard Station": "Kryjówka",
  "Store House": "Magazyny",
  "Warrior": "Wojownik",
  "Cavalryman": "Kawalerzysta",
  "Defender": "Obrońca",
  "Archer": "Strzelec",
  "Scout": "Szybkość",
  "Merchant": "Bankowość",
  "Mystic": "Maszyny",
  "Gold": "Złoto",
  "Iron": "Żelazo",
  "Wood": "Drewno",
  "Food": "Pożywienie",
  "People": "Ludzie"
}

Translation["TR"] = {
  "Pikeman": "Mızraklı piyade",
  "Swordsman": "Kılıçlı piyade",
  "Axeman": "Baltalı piyade",
  "Maceman": "Gürzlü piyade",
  "Ogre": "Savaş-Baltalı piyade",
  "Berserker": "Teoutonik şövalyesi",
  "Golem": "Özel ünite",
  "Rogue": "Yeniçeri",
  "Quickwalker": "Kolcu",
  "Light Cavalry": "Hafif süvari",
  "Heavy Cavalry": "Ağır süvari",
  "Warmage": "Koç boynuzu",
  "Arcanist": "Balistik ok",
  "Fireslinger": "Katapult(Büyük Mancınık)",
  "Stormcaller": "Büyük Sehpalı Yay",
  "Shortbowman": "Kısa menzilli okçu",
  "Longbowman": "Uzun menzilli okçu",
  "Crossbowman": "Tatar okçusu",
  "Cavalry Archer": "Okçu süvari",
  "Nobleman": "Soylu",
  "High Priest": "Tangra kahini",
  "Airweaver": "Keşiş",
  "Elf Lord": "Davulcu",
  "Warlord": "Kumandan",
  "Grand Magus": "Kuşatma kulesi",
  "Unit 26": "Samuray",
  "Unit 27": "Han Koruması",
  "Gold Mine": "Altın madeni",
  "Iron Mine": "Demir madeni",
  "Lumber Mill": "Oduncu Kampları",
  "Farm": "Çiftlikler",
  "Homes": "Evler",
  "Barracks": "Askeriye",
  "Stables": "At ahırları",
  "Guild": "Atölye",
  "Market": "Pazar yeri",
  "Training Grounds": "Demirci Atölyesi",
  "Hospital": "Hastahane",
  "Wall": "Sur",
  "Order": "Akademi",
  "Guard Station": "Sığınak",
  "Store House": "Depolar",
  "Warrior": "Savaşçı",
  "Cavalryman": "Süvari ",
  "Defender": "Savunma",
  "Archer": "Okçu",
  "Scout": "Kaşiflik",
  "Merchant": "Ekonomi",
  "Mystic": "Kuşatma",
  "Gold": "Altın",
  "Iron": "Demir",
  "Wood": "Odun",
  "Food": "Gıda",
  "People": "Kişi"
}

Translation["IT"] = {
  "Pikeman": "Picchiere",
  "Swordsman": "Spadaccino",
  "Axeman": "Soldato con ascia",
  "Maceman": "Soldato con mazza",
  "Ogre": "Asciere da Guerra",
  "Berserker": "Cavaliere teutonico",
  "Golem": "Huscarlo",
  "Rogue": "Mujahideen",
  "Quickwalker": "Esploratore",
  "Light Cavalry": "Cavaliere leggero",
  "Heavy Cavalry": "Cavaliere pesante",
  "Warmage": "Ariete",
  "Arcanist": "Ballista",
  "Fireslinger": "Catapulta",
  "Stormcaller": "Trabucco",
  "Shortbowman": "Arciere leggero",
  "Longbowman": "Arciere pesante",
  "Crossbowman": "Balestriere",
  "Cavalry Archer": "Arciere a cavallo",
  "Nobleman": "Nobiluomo",
  "High Priest": "Sacerdote Tangra",
  "Airweaver": "Prete",
  "Elf Lord": "Tamburino",
  "Warlord": "Signore della Guerra",
  "Grand Magus": "Torre d´assedio",
  "Unit 26": "Samurai",
  "Unit 27": "Guardia del Khan",
  "Gold Mine": "Miniera d´oro",
  "Iron Mine": "Miniera di ferro",
  "Lumber Mill": "Taglialegna",
  "Farm": "Fattorie",
  "Homes": "Abitazioni",
  "Barracks": "Caserma",
  "Stables": "Stalla",
  "Guild": "Officina",
  "Market": "Mercato",
  "Training Grounds": "Fabbro",
  "Hospital": "Infermeria",
  "Wall": "Mura",
  "Order": "Ordine",
  "Guard Station": "Rifugio",
  "Store House": "Magazzino",
  "Warrior": "Combattimento",
  "Cavalryman": "Cavaliere",
  "Defender": "Difesa",
  "Archer": "Precisione",
  "Scout": "Esplorazione",
  "Merchant": "Finanza",
  "Mystic": "Assedio",
  "Gold": "Oro",
  "Iron": "Ferro",
  "Wood": "Legno",
  "Food": "Cibo",
  "People": "Uomini"
}

Translation["DE"] = {
  "Pikeman": "Pikenier",
  "Swordsman": "Schwertkämpfer",
  "Axeman": "Axtkämpfer",
  "Maceman": "Keulenträger",
  "Ogre": "Hellebardist",
  "Berserker": "Kreuzritter",
  "Golem": "Huskarl",
  "Rogue": "Janitschar",
  "Quickwalker": "Späher",
  "Light Cavalry": "Leichte Kavallerie",
  "Heavy Cavalry": "Schwere Kavallerie",
  "Warmage": "Rammbock",
  "Arcanist": "Balliste",
  "Fireslinger": "Katapult",
  "Stormcaller": "Trebuchet",
  "Shortbowman": "Bogenschütze",
  "Longbowman": "Langbogenschütze",
  "Crossbowman": "Armbrustschütze",
  "Cavalry Archer": "Berittener Bogenschütze",
  "Nobleman": "Adliger",
  "High Priest": "Thangra-Priester",
  "Airweaver": "Mönch",
  "Elf Lord": "Trommler",
  "Warlord": "Kriegsfürst",
  "Grand Magus": "Belagerungsturm",
  "Unit 26": "Samurai",
  "Unit 27": "Wächter Dschingis Khans",
  "Gold Mine": "Goldgrube",
  "Iron Mine": "Eisenmine",
  "Lumber Mill": "Holzfällerhütte",
  "Farm": "Bauernhöfe",
  "Homes": "Häuser",
  "Barracks": "Kaserne",
  "Stables": "Pferdestall",
  "Guild": "Waffenschmiede",
  "Market": "Markt",
  "Training Grounds": "Schmiede",
  "Hospital": "Lazarett",
  "Wall": "Burgmauer",
  "Order": "Adelsorden",
  "Guard Station": "Versteck",
  "Store House": "Lagerhaus",
  "Warrior": "Krieger",
  "Cavalryman": "Kavallerist",
  "Defender": "Verteidiger",
  "Archer": "Schütze",
  "Scout": "Späher",
  "Merchant": "Finanzen",
  "Mystic": "Belagerung",
  "Gold": "Gold",
  "Iron": "Eisen",
  "Wood": "Holz",
  "Food": "Nahrung",
  "People": "Leute"
}

Translation["BG"] = {
  "Pikeman": "Пиконосец",
  "Swordsman": "Мечоносец",
  "Axeman": "Боец брадва",
  "Maceman": "Боец Боздуган",
  "Ogre": "Алебардист",
  "Berserker": "Тевтонски рицар",
  "Golem": "Хускарл",
  "Rogue": "Еничар",
  "Quickwalker": "Бързоход",
  "Light Cavalry": "Лека кавалерия",
  "Heavy Cavalry": "Тежка кавалерия",
  "Warmage": "Таран",
  "Arcanist": "Балиста",
  "Fireslinger": "Катапулт",
  "Stormcaller": "Трибучет",
  "Shortbowman": "Стрелец малък лък",
  "Longbowman": "Стрелец голям лък",
  "Crossbowman": "Арбалетист",
  "Cavalry Archer": "Стрелец конник",
  "Nobleman": "Благородник",
  "High Priest": "Жрец на Тангра",
  "Airweaver": "Свещенник",
  "Elf Lord": "Барабанист",
  "Warlord": "Карвед",
  "Grand Magus": "Обсадна кула",
  "Unit 26": "Самурай",
  "Unit 27": "Пазител на Хана",
  "Gold Mine": "Златна мина",
  "Iron Mine": "Желязна мина",
  "Lumber Mill": "Дървосекачи",
  "Farm": "Ферми",
  "Homes": "Жилища",
  "Barracks": "Казарма",
  "Stables": "Конюшня",
  "Guild": "Работилница",
  "Market": "Пазар",
  "Training Grounds": "Ковачница",
  "Hospital": "Лазарет",
  "Wall": "Стена",
  "Order": "Орден",
  "Guard Station": "Скривалище",
  "Store House": "Складове",
  "Warrior": "Войн",
  "Cavalryman": "Кавалерист",
  "Defender": "Защитник",
  "Archer": "Стрелец",
  "Scout": "Скаут",
  "Merchant": "Финанси",
  "Mystic": "Обсада",
  "Gold": "Злато",
  "Iron": "Желязо",
  "Wood": "Дърва",
  "Food": "Храна",
  "People": "Популация"
}

Translation["HR"] = {
  "Pikeman": "Pikeman",
  "Swordsman": "Swordsman",
  "Axeman": "Axeman",
  "Maceman": "Maceman",
  "Ogre": "Battle-Axemen",
  "Berserker": "Teoutonic knight",
  "Golem": "Huskarle",
  "Rogue": "Janjičar",
  "Quickwalker": "Quickwalkers",
  "Light Cavalry": "Light cavalry",
  "Heavy Cavalry": "Heavy cavalry",
  "Warmage": "Ram",
  "Arcanist": "Ballistician",
  "Fireslinger": "Catapult",
  "Stormcaller": "Trebuchet",
  "Shortbowman": "Shortbow archer",
  "Longbowman": "Longbow archer",
  "Crossbowman": "Crossbow archer",
  "Cavalry Archer": "Archer cavalry",
  "Nobleman": "Plemić",
  "High Priest": "Tangra svećenik",
  "Airweaver": "Svećenik",
  "Elf Lord": "Bubnjar",
  "Warlord": "Vojni Zapovjednik",
  "Grand Magus": "Opsadni Toranj",
  "Unit 26": "Samuraj",
  "Unit 27": "KhanGuard",
  "Gold Mine": "Rudnik zlata",
  "Iron Mine": "Rudnik željeza",
  "Lumber Mill": "Drvarnica",
  "Farm": "Farma",
  "Homes": "Nastambe",
  "Barracks": "Vojarna",
  "Stables": "Staja",
  "Guild": "Radionica",
  "Market": "Tržnica",
  "Training Grounds": "Kovačnica",
  "Hospital": "Ambulanta",
  "Wall": "Zid",
  "Order": "Crkva",
  "Guard Station": "Sklonište",
  "Store House": "Skladišta",
  "Gold": "Zlato",
  "Iron": "Željezo",
  "Wood": "Drvo",
  "Food": "Hrana",
  "People": "Ljudi",
  "Warrior": "Ratnik",
  "Cavalryman": "Calvaryman",
  "Defender": "Obranioc",
  "Archer": "Strijelac",
  "Scout": "Izviđač",
  "Merchant": "Financije",
  "Mystic": "Opsada"
}

Translation["LT"] = {
  "Pikeman": "Ietininkai",
  "Swordsman": "Kariai su kardais",
  "Axeman": "Kariai su kirviais",
  "Maceman": "Vėzdininkai",
  "Ogre": "Alebardininkai",
  "Berserker": "Kryžiuočių riteriai",
  "Golem": "Huskarlai",
  "Rogue": "Janičarai",
  "Quickwalker": "Šnipai",
  "Light Cavalry": "Lengvoji kavalerija",
  "Heavy Cavalry": "Sunkioji kavalerija",
  "Warmage": "Taranai",
  "Arcanist": "Balista",
  "Fireslinger": "Katapultos",
  "Stormcaller": "Trebušetai",
  "Shortbowman": "Šauliai su mažu lanku",
  "Longbowman": "Šauliai su dideliu lanku",
  "Crossbowman": "Arbaletininkai",
  "Cavalry Archer": "Raiti šauliai",
  "Nobleman": "Bajorai",
  "High Priest": "Tangra kunigas",
  "Airweaver": "Vienuoliai",
  "Elf Lord": "Būgnininkai",
  "Warlord": "Karvedžiai",
  "Grand Magus": "Apgulties bokštai",
  "Unit 26": "Samurajai",
  "Unit 27": "Sargybinis",
  "Gold Mine": "Aukso kasykla",
  "Iron Mine": "Geležies kasykla",
  "Lumber Mill": "Medkirčių namelis",
  "Farm": "Ferma",
  "Homes": "Namai",
  "Barracks": "Kareivinės",
  "Stables": "Arklidės",
  "Guild": "Dirbtuvės",
  "Market": "Turgus",
  "Training Grounds": "Kalvė",
  "Hospital": "Gydykla",
  "Wall": "Miesto siena",
  "Order": "Orderis",
  "Guard Station": "Slėptuvė",
  "Store House": "Sandėlis",
  "Warrior": "Karys",
  "Cavalryman": "Raitelis",
  "Defender": "Gynėjas",
  "Archer": "Šaulys",
  "Scout": "Žvalgas",
  "Merchant": "Ekonomistas",
  "Mystic": "Apgulties specialistas",
  "Gold": "Aukso",
  "Iron": "Geležies",
  "Wood": "Medienos",
  "Food": "Maisto",
  "People": "Žmonių"
}

Translation["NL"] = {
  "Pikeman": "Speerman",
  "Swordsman": "Zwaardman",
  "Axeman": "Bijlman",
  "Maceman": "Knotsman",
  "Ogre": "Gevechts-Bijlman",
  "Berserker": "Germaanse ridder",
  "Golem": "Huskarl",
  "Rogue": "Janitsaren",
  "Quickwalker": "Snelloper",
  "Light Cavalry": "Lichte cavalerie",
  "Heavy Cavalry": "Zware cavalerie",
  "Warmage": "Ram",
  "Arcanist": "Ballista",
  "Fireslinger": "Katapult",
  "Stormcaller": "Blijde",
  "Shortbowman": "Kortboog schutter",
  "Longbowman": "Langboog Sch.",
  "Crossbowman": "Kruisboogschutter",
  "Cavalry Archer": "Boogschutter te paard",
  "Nobleman": "Edelman",
  "High Priest": "Tangra priester",
  "Airweaver": "Heilige",
  "Elf Lord": "Drummer",
  "Warlord": "Krijgsheer",
  "Grand Magus": "Belegerings Toren",
  "Unit 26": "Samoerai",
  "Unit 27": "KhanBewaker",
  "Gold Mine": "Goudmijn",
  "Iron Mine": "IJzermijn",
  "Lumber Mill": "Houthakkers",
  "Farm": "Boerderijen",
  "Homes": "Huizen",
  "Barracks": "Barakken",
  "Stables": "Stal",
  "Guild": "Werkplaats",
  "Market": "Marktplaats",
  "Training Grounds": "Smederij",
  "Hospital": "Ziekenhuis",
  "Wall": "Muur",
  "Order": "Klooster",
  "Guard Station": "Schuilplaats",
  "Store House": "Opslagplaatsen",
  "Warrior": "Strijder",
  "Cavalryman": "Cavalerist",
  "Defender": "Verdediger",
  "Archer": "Boogschutter",
  "Scout": "Verkenner",
  "Merchant": "Financiën",
  "Mystic": "Belegering",
  "Gold": "Goud",
  "Iron": "IJzer",
  "Wood": "Hout",
  "Food": "Voedsel",
  "People": "Aantal"
}

Translation["NO"] = {
    "Pikeman": "Pikener",
  "Swordsman": "Sverdmann",
  "Axeman": "Øksemann",
  "Maceman": "Stridsklubbemann",
  "Ogre": "Stridsøksemann",
  "Berserker": "Korsfarer",
  "Golem": "Huskarl",
  "Rogue": "Mujahedin",
  "Quickwalker": "Speider",
  "Light Cavalry": "Lett kavaleri",
  "Heavy Cavalry": "Tungt kavaleri",
  "Warmage": "Rambukk",
  "Arcanist": "Ballista",
  "Fireslinger": "Katapult",
  "Stormcaller": "Blide",
  "Shortbowman": "Kortbueskytter",
  "Longbowman": "Langbueskytter",
  "Crossbowman": "Armbrøstskytter",
  "Cavalry Archer": "Ridende bueskyttere",
  "Nobleman": "Adelsmann",
  "High Priest": "Tangraprest",
  "Airweaver": "Munk",
  "Elf Lord": "Trommeslager",
  "Warlord": "Krigsherre",
  "Grand Magus": "Beleiringstårn",
  "Unit 26": "Samurai",
  "Unit 27": "Khanvakt",
  "Gold Mine": "Gullgruve",
  "Iron Mine": "Jerngruve",
  "Lumber Mill": "Vedbod",
  "Farm": "Gårdsbruk",
  "Homes": "Boliger",
  "Barracks": "Kaserne",
  "Stables": "Stall",
  "Guild": "Verksted",
  "Market": "Markedsplass",
  "Training Grounds": "Smie",
  "Hospital": "Sykehus",
  "Wall": "Bymur",
  "Order": "Orden",
  "Guard Station": "Gjemmested",
  "Store House": "Lager",
  "Warrior": "Kriger",
  "Cavalryman": "Kavalerist",
  "Defender": "Forsvarer",
  "Archer": "Bueskytter",
  "Scout": "Hurtighet",
  "Merchant": "Økonomi",
  "Mystic": "Beleiring",
  "Gold": "Gull",
  "Iron": "Jern",
  "Wood": "Tre",
  "Food": "Mat",
  "People": "Folk"
}

Translation["SE"] = {
  "Pikeman": "Falanx",
  "Swordsman": "Svärdsman",
  "Axeman": "Yxman",
  "Maceman": "Klubbkämpe",
  "Ogre": "KrigsYxman",
  "Berserker": "Korsriddare",
  "Golem": "Livgardet",
  "Rogue": "Mujahideen",
  "Quickwalker": "Spejare",
  "Light Cavalry": "Lätt kavalleri",
  "Heavy Cavalry": "Tungt Kavalleri",
  "Warmage": "Murbräcka",
  "Arcanist": "Armborstmaskin",
  "Fireslinger": "Katapult",
  "Stormcaller": "Träbock",
  "Shortbowman": "Bågskytt",
  "Longbowman": "Långbågskytt",
  "Crossbowman": "Armborstskytt",
  "Cavalry Archer": "Ridande bågskytt",
  "Nobleman": "Adelsman",
  "High Priest": "Tangrapräst",
  "Airweaver": "undefined",
  "Elf Lord": "Trummare",
  "Warlord": "Krigsherre",
  "Grand Magus": "Belägringstorn",
  "Unit 26": "Samuraj",
  "Unit 27": "KhanVakt",
  "Gold Mine": "Guldgruva",
  "Iron Mine": "Järngruva",
  "Lumber Mill": "Skogshygge",
  "Farm": "Jordbruk",
  "Homes": "Bostäder",
  "Barracks": "Kasern",
  "Stables": "Stall",
  "Guild": "Verkstad",
  "Market": "Marknad",
  "Training Grounds": "Smedja",
  "Hospital": "Sjukstuga",
  "Wall": "Stadsmur",
  "Order": "Orden",
  "Guard Station": "Gömställe",
  "Store House": "Magasin",
  "Warrior": "Krigare",
  "Cavalryman": "Kavalleriryttare",
  "Defender": "Försvarare",
  "Archer": "Bågskytt",
  "Scout": "Snabbhet",
  "Merchant": "Ekonomi",
  "Mystic": "Belägring",
  "Gold": "Guld",
  "Iron": "Järn",
  "Wood": "Trä",
  "Food": "Mat",
  "People": "Folk"
}

Translation["UA"] = {
  "Pikeman": "Списник",
  "Swordsman": " Мечник ",
  "Axeman": "Дружинник ",
  "Maceman": "Воїн з булавою",
  "Ogre": "Дружинник із сокирою ",
  "Berserker": "Тевтонський лицар",
  "Golem": "Хускарл ",
  "Rogue": "Яничар",
  "Quickwalker": "Розвідник",
  "Light Cavalry": "Легка кавалерія",
  "Heavy Cavalry": "Важка кавалерія ",
  "Warmage": "Таран",
  "Arcanist": "Баліста",
  "Fireslinger": "Катапульта",
  "Stormcaller": "Требушет ",
  "Shortbowman": "Лучник з коротким луком",
  "Longbowman": "Лучник з довгим луком",
  "Crossbowman": "Арбалетник",
  "Cavalry Archer": "Кінний лучник",
  "Nobleman": "Аристократ ",
  "High Priest": "Жрець Тангри",
  "Airweaver": "Чернець",
  "Elf Lord": "Барабанщик ",
  "Warlord": "Полководець",
  "Grand Magus": "Облогова Вежа",
  "Unit 26": "Самурай",
  "Unit 27": "KhanGuard",
  "Gold Mine": "Золота шахта",
  "Iron Mine": "Залізний рудник",
  "Lumber Mill": "Лісопилка",
  "Farm": "Ферма",
  "Homes": "Поселення ",
  "Barracks": "Казарма",
  "Stables": "Стайня ",
  "Guild": "Майстерня",
  "Market": "Ринок",
  "Training Grounds": "Кузня",
  "Hospital": "Лазарет ",
  "Wall": "Стіна",
  "Order": "Орден",
  "Guard Station": "Схованка ",
  "Store House": "Склади ",
  "Warrior": "Воїн ",
  "Cavalryman": "Кавалерист ",
  "Defender": "Захисник",
  "Archer": "Лучник",
  "Scout": "Скаут",
  "Merchant": "Фінанси",
  "Mystic": "Облога",
  "Gold": "Золото",
  "Iron": "Залізо",
  "Wood": "Дерево",
  "Food": "Провізія",
  "People": "Населення"
}

Translation["HU"] = {
  "Pikeman": "Lándzsás",
  "Swordsman": "Kardforgató",
  "Axeman": "Fejszés",
  "Maceman": "Buzogányos",
  "Ogre": "Alabárdos",
  "Berserker": "Teutonlovag",
  "Golem": "Huscarl",
  "Rogue": "Janicsár",
  "Quickwalker": "Gyorsfutó",
  "Light Cavalry": "Könnyű lovasság",
  "Heavy Cavalry": "Nehéz lovasság",
  "Warmage": "Faltörő kos ",
  "Arcanist": "Balliszta",
  "Fireslinger": "Katapult",
  "Stormcaller": "Trebuchet",
  "Shortbowman": "Könnyű íjász",
  "Longbowman": "Nehéz íjász",
  "Crossbowman": "Nyílpuskás íjász",
  "Cavalry Archer": "Lovasíjász",
  "Nobleman": "Nemes",
  "High Priest": "Tangra papja",
  "Airweaver": "Pap",
  "Elf Lord": "Dobos",
  "Warlord": "Hadvezér",
  "Grand Magus": "Ostrom Torony",
  "Unit 26": "Szamuráj",
  "Unit 27": "KhánŐrség",
  "Gold Mine": "Aranybánya",
  "Iron Mine": "Vasbánya",
  "Lumber Mill": "Fatelep",
  "Farm": "Farmok",
  "Homes": "Lakóházak",
  "Barracks": "Kaszárnya",
  "Stables": "Istálló",
  "Guild": "Műhely",
  "Market": "Piac",
  "Training Grounds": "Fegyverkovács",
  "Hospital": "Katonai kórház",
  "Wall": "Várfal",
  "Order": "Rendház",
  "Guard Station": "Rejtekhely",
  "Store House": "Raktárak",
  "Warrior": "Harcos",
  "Cavalryman": "Huszár",
  "Defender": "Védő",
  "Archer": "Íjász",
  "Scout": "Felderítő",
  "Merchant": "Penzügy",
  "Mystic": "Ostrom",
  "Gold": "arany",
  "Iron": "vas",
  "Wood": "Fa",
  "Food": "ennivaló",
  "People": "Ember/ek"
}

Translation["AE"] = {
  "Pikeman": "مقاتل الرمح",
  "Swordsman": "مقاتل السيف",
  "Axeman": "مقاتل الفأس",
  "Maceman": "مقاتل الصولجان",
  "Ogre": "مقاتل الفأس",
  "Berserker": "فرسان التيوتونيك",
  "Golem": "الفايكنج",
  "Rogue": "الإنكشارية",
  "Quickwalker": "جواسيس",
  "Light Cavalry": "الفرسان الخفيفة",
  "Heavy Cavalry": "الفرسان الثقيلة",
  "Warmage": "محطمة الابواب",
  "Arcanist": "مقلاع السهم العملاق",
  "Fireslinger": "المنجنيق",
  "Stormcaller": "مقلاع الحجارة",
  "Shortbowman": "رماة القوس القصير",
  "Longbowman": "رماة القوس الطويل",
  "Crossbowman": "رماة القوس المتقطع",
  "Cavalry Archer": "فرسان الاسهم",
  "Nobleman": "النبلاء",
  "High Priest": "الرجل الصالح",
  "Airweaver": "متعبد",
  "Elf Lord": "طبال",
  "Warlord": "امير الحرب",
  "Grand Magus": "ابراج الدفاع",
  "Unit 26": "الساموراي",
  "Unit 27": "حرس الملك",
  "Gold Mine": "منجم الذهب",
  "Iron Mine": "منجم الحديد",
  "Lumber Mill": "معمل النشارة",
  "Farm": "المزارع",
  "Homes": "المساكن",
  "Barracks": "الثكنات",
  "Stables": "الاسطبل",
  "Guild": "الورشة",
  "Market": "السوق",
  "Training Grounds": "الحداد",
  "Hospital": "المستشفى",
  "Wall": "الحائط",
  "Order": "قصر الحكم",
  "Guard Station": "المخبأ",
  "Store House": "المخازن",
  "Warrior": "القتال",
  "Cavalryman": "الفروسية",
  "Defender": "المدافع",
  "Archer": "الرماة",
  "Scout": "الخيول",
  "Merchant": "الموارد",
  "Mystic": "فك الحصار",
  "Gold": "الذهب",
  "Iron": "الحديد",
  "Wood": "الخشب",
  "Food": "الطعام",
  "People": "الافراد"
};

Translation["IL"] = {
  "Pikeman": "כִּידוֹנאי",
  "Swordsman": "סַיָּף",
  "Axeman": "גַּרְזֶנאי",
  "Maceman": "איש השרביט",
  "Ogre": "איש הגרזן",
  "Berserker": "אביר טֶוְטוֹנִי",
  "Golem": "סקסוני",
  "Rogue": "ג'נייס",
  "Quickwalker": "פרש ברק",
  "Light Cavalry": "חֵיל פָּרָשִׁים קל",
  "Heavy Cavalry": "חֵיל פָּרָשִׁים כבד",
  "Warmage": "אֵיל הבַּרְזֶל",
  "Arcanist": "בליסטיקן",
  "Fireslinger": "מקלעת כבדה",
  "Stormcaller": "טרמיניטור",
  "Shortbowman": "קַשָּׁת קָצָר טְוָח",
  "Longbowman": "קַשָּׁת אָרוךְ טְוָח",
  "Crossbowman": "קשת מוצלב",
  "Cavalry Archer": "פרש-קשת",
  "Nobleman": "איש אצולה",
  "High Priest": "כומר טנגרה",
  "Airweaver": "נזיר",
  "Elf Lord": "מתופף",
  "Warlord": "Warlord",
  "Grand Magus": "מגדל מצור",
  "Unit 26": "סמוראי",
  "Unit 27": "KhanGuard",
  "Gold Mine": "מכרה זהב",
  "Iron Mine": "מכרה ברזל",
  "Lumber Mill": "מכרה עץ",
  "Farm": "חוות",
  "Homes": "בתי מגורים",
  "Barracks": "בסיס צבאי",
  "Stables": "אֻרְוות",
  "Guild": "בֵּית מְלָאכָה",
  "Market": "שׁוּק",
  "Training Grounds": "בית הנפח",
  "Hospital": "בית חולים",
  "Wall": "חומה",
  "Order": "מיסדר",
  "Guard Station": "מסתור",
  "Store House": "בתי אִחְסוּן",
  "Warrior": "לוֹחֵם",
  "Cavalryman": "ה-פָּרָשִׁ",
  "Defender": "מֵגֵן",
  "Archer": "קַשָּׁת",
  "Scout": "סַיָּר",
  "Merchant": "כְּסָפִים",
  "Mystic": "מָצוֹר",
  "Gold": "זהב",
  "Iron": "ברזל",
  "Wood": "עצים",
  "Food": "אוכל",
  "People": "חיילים"
}

Translation["IR"] = {
  "Pikeman": "نیزه دار",
  "Swordsman": "شمشیرزن",
  "Axeman": "تبرزن",
  "Maceman": "گرز دار",
  "Ogre": "تبر زن جنگی",
  "Berserker": "شوالیه توتنی",
  "Golem": "گارد مخصوص",
  "Rogue": "سرباز پياده نظام",
  "Quickwalker": "جاسوسان",
  "Light Cavalry": "سواره نطام سبک",
  "Heavy Cavalry": "سواره نظام سنگین",
  "Warmage": "دژکوب",
  "Arcanist": "بالستیک",
  "Fireslinger": "منجنیق",
  "Stormcaller": "منجنيق قلعه کوش",
  "Shortbowman": "کمانداران با کمان های کوتاه",
  "Longbowman": "کماندار با کمان بلند",
  "Crossbowman": "کمان گیر با کمان صليبى",
  "Cavalry Archer": "کماندار سواره",
  "Nobleman": "نجیب زاده",
  "High Priest": "کشیش تانگارا",
  "Airweaver": "راهب",
  "Elf Lord": "کوس زن",
  "Warlord": "ارباب جنگ",
  "Grand Magus": "برج محاصره",
  "Unit 26": "سامورائی",
  "Unit 27": "گارد خان",
  // buildings
  "Gold Mine": "معدن طلا",
  "Iron Mine": "معدن آهن",
  "Lumber Mill": "چوب بری",
  "Farm": "مزرعه",
  "Homes": "خانه",
  "Barracks": "سربازخانه",
  "Stables": "اصطبل ها",
  "Guild": "کارگاه",
  "Market": "بازار",
  "Training Grounds": "آهنگری",
  "Hospital": "درمانگاه",
  "Wall": "دیوار",
  "Order": "قصر",
  "Guard Station": "پناهگاه",
  "Store House": "انبارها",
  // resources
  "Gold": "طلا",
  "Iron": "آهن",
  "Wood": "چوب",
  "Food": "غذا",
  "People": "تعدادافراد",
  // hero skills
  "Warrior": "جنگجو",
  "Cavalryman": "جنجگوی سواره",
  "Defender": "مدافع",
  "Archer": "کماندار",
  "Scout": "پيشاهنگ",
  "Merchant": "سرمايه گذارى",
  "Mystic": "محاصره",
  //// the rest of these are the way the script interfaces with the user
//// any meaningful translation will do

  // abbreviations for days of the week; can be blank strings if none desired
  "Mo": "دوشنبه",
  "Tu": "سه شنبه",
  "We": "چهار شنبه",
  "Th": "پنج شنبه",
  "Fr": "جمعه",
  "Sa": "شنبه",
  "Su": "یکشنبه",
  // script interface--might include HTML markup
  "Convert Res": "تبدیل منابع",
  "Send Res": "ارسال منابع",
  "Overview": "نگاه کلی",
  "Clan Forum": "فوروم",
  "Clan Admin": "Clan Admin",
  "VIP Search": "جستجوی VIP",
  "Release Troops": "مارش ها",
  "Server": "سرور",
  "One-Way": "يک جانبه",
  "<i>Estimated</i> Server Arrival": "<i>Estimated</i> Server Arrival",
  "Total Res": "کل منابع",
  "Max Merchant": "مقدار منابع قابل انتقال",
  "Extra": "مقدار اضافی",
  "Warning: not enough troops to paste all.": "اخطار: تعداد سربازان کافی نیست",
  "Default attack formation not found.": "سبک ارتش پیش فرض موجود نیست",
  "Quick Roads": "جاده سریع",
  "Copy Skills": "کپی مهارت ها",
  "Saved": "ذخیره",
  "Options": "تنظیمات",
  "Minimum Heal": "کمترین شفا دادن",
  "Minimum Scavenge": "کمترین پاکسازی",
  "Steal Fraction": "درصد سرقت",
  "World Speed": "سرعت دنیا",
  "ClanID": "ClanID",
  "Server Offset, Minutes": "Server Offset, Minutes",
  "Maximum X": "Maximum X",
  "Maximum Y": "Maximum Y",
  "Default Attack Formation": "سبک ارتش پیش فرض",
  "Delete ALL Data (Map Too)": "پاک کردن همه اطلاعات و نقشه",
  "For world": "برای جهان",
  "Really! (This could take a long time.)": "Really! (This could take a long time.)",
  "Paste Formation": "Paste Formation",
  // troop selection tools
  "All": "همه",
  "None": "هیچ",
  "Single Infantry": "یک پیاده نظام",
  "Paste": "Paste",
  "Invert": "برعکس",
  "Formation": "سبک ارتش",
  "Default": "پیش فرض",
  // shortcut for speed-modified marches: attack, scavenge, transfer
  "A": "حمله",
  "S": "پاکسازی",
  "T": "انتقال منابع",
  // additional march counters
  "Speed": "سرعت",
  "Damage": "خسارت",
  "Mystics": "محاصره",
  "Encumbrance": "Encumbrance",
  "Non-Mystics": "بدون محاصره",
  "Castles": "قلعه",
  "Effective Speed": "سرعت موثر",
  "Server Arrival": "Server Arrival",
  "Server Return": "Server Return",
  "Local Arrival": "Local Arrival",
  "Local Return": "Local Return",
  "Timing (no coins)": "Timing (no coins)",
  "Cancel": "لغو",
  "Could not find that march type.": "Could not find that march type.",
  "Cannot transfer.": "Cannot transfer.",
  // abbreviations for light cavalry, cavalry archer
  "LC": "سواره نظام سبک",
  "CA": "کماندار سواره",
  // merchant management
  "Save": "ذخیره",
  "Delete": "پاک کردن",
  "Top Up": "بیشتر بالا",
  "Distribute Evenly": "پخش به طور مساوی",
  "Reset": "Reset",
  "Room Left": "Room Left",
  "Profit Ratio": "نرخ سود",
  "Source": "منبع",
  // battle reports
  "Non-Junk (Res)": "تقسیم منابع",
  "Total Attacker Loss (Res/Pop)": "کل ضرر مهاجم (Res/Pop)",
  "Total Defender Loss (Res/Pop)": "کل ضرر مدافع (Res/Pop)",
  "Estimated Total Scavenge": "مقدار پاک سازی کل تخمینی",
  "Survivor Capacity": "ظرفیت بازمانده ها",
  "Copy Formation": "کپی سبک ",
  "Copy Army": "کپی نیروها",
  "Total Pop": "جمعیت کل",
  // battle report show stats inline
  "Inf": "پیاده نظام", // damage vs. infantry
  "Cav": "سوار نظام", // damage vs. cavalry
  "Arch": "کماندار", // damage vs. archers
  "Myst": "محاصره", // damage vs. mystics
  "Atk+": "حمله با ارتقا ", // unit attack upgrades
  "Def+": "دفاع ارتقا ", // unit defense upgrades
  "Life": "سلامت", // unit life
  "Lost Resources": "منابع از دست  رفته",
  "Lost Pop": "جمعیت ازدست رفته",
  "Lost Life": "سلامت از دست رفته",
  "Estimated Scavenge": "پاکسازی تخمینی",
  // sim
  "Min Unit": "حد اقل نیرو",
  "Paste Unit": "Paste Unit",
  "Add Unit": "اضافه کردن نیرو",
  "Max Upgrade": "بیشترین ارتقا ",
  "Paste Skill": "Paste Skill",
  "Max Skill": "بیشترین مهارت",
  // unit training
  "Total Resources to Train": "منابع کل برای آموزش",
  "Total Time to Train": "زمان کل برای آموزش",
  "Total Cancel": "لغو کلی",
  // map interface
  "Fastest": "سریع مثل صاعقه",
  "Slowest": "پیاده رفتن",
  "Player": "بازیکن",
  "Level": "سطح",
  "Castle": "قلعه",
  "Capital": "پایتخت",
  "Clan": "قبیله",
  "Last Activity (Days)": "آخرین زمان آنلاین بودن به روز",
  "Last ms": "Last ms", // last known activity, JS time format
  "Data Grabbed (Days)": "Data Grabbed (Days)",
  "Grabbed ms": "Grabbed ms", // data grabbed, JS time format
  "Generate Map Report": "Generate Map Report",
  // spy reports
  "Message Age": "سن پیام",
  "Copy": "کپی",
  "Delete Page": "پاک کردن صفحه",
  "Scavenge": "پاکسازی" // i.e., clean-up
}

Translation["JP"] = {
  "Pikeman": "槍兵",
  "Swordsman": "剣兵",
  "Axeman": "斧兵",
  "Maceman": "戦棍兵",
  "Ogre": "戦斧兵",
  "Berserker": "チュートンナイト",
  "Golem": "ハスカール",
  "Rogue": "ムジャヒディン",
  "Quickwalker": "斥候騎兵",
  "Light Cavalry": "軽騎兵隊",
  "Heavy Cavalry": "重騎兵隊",
  "Warmage": "大型衝車",
  "Arcanist": "小型衝車",
  "Fireslinger": "大型投石器",
  "Stormcaller": "小型投石器",
  "Shortbowman": "弓兵(ショートボウ)",
  "Longbowman": "弓兵(ロングボウ)",
  "Crossbowman": "弓兵(クロスボウ)",
  "Cavalry Archer": "弓騎兵",
  "Nobleman": "貴族",
  "High Priest": "テーングラー僧",
  "Airweaver": "聖職者",
  "Elf Lord": "ドラマー",
  "Warlord": "将軍",
  "Grand Magus": "包囲塔",
  "Unit 26": "侍",
  "Unit 27": "カンの護衛",
  "Gold Mine": "金鉱",
  "Iron Mine": "鉄鉱",
  "Lumber Mill": "伐採場",
  "Farm": "農場",
  "Homes": "住居",
  "Barracks": "兵舎",
  "Stables": "騎兵訓練所",
  "Guild": "作業場",
  "Market": "市場",
  "Training Grounds": "鍛冶屋",
  "Hospital": "診療所",
  "Wall": "城壁",
  "Order": "司令塔",
  "Guard Station": "隠し倉庫",
  "Store House": "倉庫",
  "Gold Mine": "金鉱",
  "Iron Mine": "鉄鉱",
  "Lumber Mill": "伐採場",
  "Farm": "農場",
  "Homes": "住居",
  "Barracks": "兵舎",
  "Stables": "騎兵訓練所",
  "Guild": "作業場",
  "Market": "市場",
  "Training Grounds": "鍛冶屋",
  "Hospital": "診療所",
  "Wall": "城壁",
  "Order": "司令塔",
  "Guard Station": "隠し倉庫",
  "Store House": "倉庫",
  "Gold": "黄金",
  "Iron": "鉄",
  "Wood": "木材",
  "Food": "食糧",
  "People": "軍人の数"
}

// select language
// if host/language not found, default Utopia Kingdoms values are used
var Lang = "";
var Protocol = window.location.protocol + "//";
var Host = Protocol + window.location.host;
var StrippedHost = /[^.]+?\.(.+)$/i.exec(window.location.hostname);
StrippedHost ? StrippedHost = StrippedHost[1] : StrippedHost = "";
switch (StrippedHost) {
  case "khanwars.com": Lang = "EN"; break;
  case "guerrakhan.com": Lang = "BR"; break;
  case "lesseigneurs.fr": Lang = "FR"; break;
  case "khanwars.com.pt": Lang = "PT"; break;
  case "khanwars.es": Lang = "ES"; break;
  case "khanwars.cl": Lang = "CL"; break;
  case "deche.vn": Lang = "VN"; break;
  case "khanwars.ro": Lang = "RO"; break;
  case "khanwars.pl": Lang = "PL"; break;
  case "hansavaslari.com": Lang = "TR"; break;
  case "khanwars.it": Lang = "IT"; break;
  case "zarenkriege.de": Lang = "DE"; break;
  case "hanovete.com": Lang = "BG"; break;
  case "khanratnik.com": Lang = "HR"; break;
  case "draugas.lt": Lang = "LT"; break;
  case "khanwars.nl": Lang = "NL"; break;
  case "khanwars.no": Lang = "NO"; break;
  case "khanwars.se": Lang = "SE"; break;
  case "pravyteli.com": Lang = "UA"; break;
  case "khanwars.hu": Lang = "HU"; break;
  case "khanwars.ae": Lang = "AE"; break;
  case "khanwars.jp": Lang = "JP"; break;
  case "khanwars.ir": Lang = "IR"; break;
  case "lordwars.co.il": Lang = "IL"; break;
}

// end internationalisation
/******************************************************************************/

// cross-domain XPath screwed up as of FF3.6
// this fix courtesy of http://angusdev.blogspot.com/2010/02/fixing-xpath-problem-in-firefox-36.html
var isFF36up = false;
if (navigator.userAgent) {
  var ffver = navigator.userAgent.match(/Firefox\/3\.(\d+)/);
  isFF36up = ffver && parseInt(ffver[1], 10) >= 6;
}
var nsResolver = {
  lookupNamespaceURI:function (prefix) {
    if (isFF36up && prefix == "ns") {
      return "http://www.w3.org/1999/xhtml";
    } else {
      return "";
    }
  }
};

// uncomment to check translation of specific language in error console
/*
for (var N in Words) {
  GM_log(Words[N] + "------------" + Translation["PT"][N])
}
*/

// global constants

var DefenderLevels = [0, 10, 15, 30];
var WarriorLevels = [0, 5, 15, 30];
var CavalryLevels = [0, 20, 30, 40];
var ArcherLevels = [0, 20, 40, 60];
var MysticLevels = [0, 10, 20, 30];
var ScoutLevels = [0, 30, 80, 150];
var MerchantLevels = [15, 12.5, 10, 6];

var Units = [];
Units["u1"] = {
  "name": Translate("Pikeman"), "pop": 1, "speed": 10, "carry": 25, "life": 50,
  "range": 1, "infantry": 10, "cavalry": 50, "archer": 10, "building": 10,
  "mystic": 10, "wallcrasher": 0,
  "gold": 30, "iron": 30, "wood": 50, "food": 15
};
Units["u2"] = {
  "name": Translate("Swordsman"), "pop": 1, "speed": 15, "carry": 15, "life": 50,
  "range": 1, "infantry": 30, "cavalry": 15, "archer": 30, "building": 10,
  "mystic": 30, "wallcrasher": 0,
  "gold": 30, "iron": 65, "wood": 30, "food": 15
};
Units["u3"] = {
  "name": Translate("Axeman"), "pop": 1, "speed": 10, "carry": 10, "life": 40,
  "range": 1, "infantry": 40, "cavalry": 5, "archer": 40, "building": 10,
  "mystic": 40, "wallcrasher": 0,
  "gold": 10, "iron": 45, "wood": 35, "food": 50
};
Units["u4"] = {
  "name": Translate("Maceman"), "pop": 1, "speed": 12, "carry": 20, "life": 40,
  "range": 1, "infantry": 35, "cavalry": 10, "archer": 35, "building": 10,
  "mystic": 35, "wallcrasher": 0,
  "gold": 45, "iron": 10, "wood": 30, "food": 50
};
Units["u5"] = {
  "name": Translate("Ogre"), "pop": 10, "speed": 12, "carry": 25, "life": 290,
  "range": 1, "infantry": 60, "cavalry": 110, "archer": 60, "building": 10,
  "mystic": 60, "wallcrasher": 0,
  "gold": 0, "iron": 300, "wood": 300, "food": 150
};
Units["u6"] = {
  "name": Translate("Berserker"), "pop": 10, "speed": 20, "carry": 15, "life": 400,
  "range": 1, "infantry": 140, "cavalry": 40, "archer": 140, "building": 10,
  "mystic": 140, "wallcrasher": 0,
  "gold": 150, "iron": 500, "wood": 100, "food": 200
};
Units["u7"] = {
  "name": Translate("Golem"), "pop": 10, "speed": 20, "carry": 15, "life": 300,
  "range": 1, "infantry": 120, "cavalry": 50, "archer": 120, "building": 10,
  "mystic": 120, "wallcrasher": 0,
  "gold": 150, "iron": 400, "wood": 150, "food": 150
};
Units["u8"] = {
  "name": Translate("Rogue"), "pop": 10, "speed": 10, "carry": 35, "life": 310,
  "range": 1, "infantry": 115, "cavalry": 90, "archer": 115, "building": 10,
  "mystic": 115, "wallcrasher": 0,
  "gold": 100, "iron": 150, "wood": 150, "food": 400
};
Units["u9"] = {
  "name": Translate("Quickwalker"), "pop": 2, "speed": 5, "carry": 1, "life": 100,
  "range": 1, "infantry": 5, "cavalry": 5, "archer": 5, "building": 0,
  "mystic": 5, "wallcrasher": 0,
  "gold": 10, "iron": 40, "wood": 45, "food": 50
};
Units["u10"] = {
  "name": Translate("Light Cavalry"), "pop": 4, "speed": 6, "carry": 90, "life": 200,
  "range": 1, "infantry": 100, "cavalry": 50, "archer": 100, "building": 0,
  "mystic": 100, "wallcrasher": 0,
  "gold": 120, "iron": 120, "wood": 50, "food": 250
};
Units["u11"] = {
  "name": Translate("Heavy Cavalry"), "pop": 6, "speed": 7, "carry": 55, "life": 500,
  "range": 1, "infantry": 250, "cavalry": 150, "archer": 250, "building": 0,
  "mystic": 250, "wallcrasher": 0,
  "gold": 300, "iron": 250, "wood": 100, "food": 100
};
Units["u12"] = {
  "name": Translate("Warmage"), "pop": 5, "speed": 25, "carry": 60, "life": 200,
  "range": 1, "infantry": 0, "cavalry": 0, "archer": 0, "building": 180,
  "mystic": 0, "wallcrasher": 1,
  "gold": 250, "iron": 500, "wood": 750, "food": 250
};
Units["u13"] = {
  "name": Translate("Arcanist"), "pop": 6, "speed": 25, "carry": 15, "life": 300,
  "range": 5, "infantry": 150, "cavalry": 150, "archer": 150,
  "building": 120, "mystic": 70, "wallcrasher": 1,
  "gold": 125, "iron": 250, "wood": 625, "food": 500
};
Units["u14"] = {
  "name": Translate("Fireslinger"), "pop": 8, "speed": 30, "carry": 15, "life": 400,
  "range": 5, "infantry": 25, "cavalry": 10, "archer": 25, "building": 110,
  "mystic": 25, "wallcrasher": 1,
  "gold": 863, "iron": 250, "wood": 500, "food": 250
};
Units["u15"] = {
  "name": Translate("Stormcaller"), "pop": 10, "speed": 30, "carry": 15, "life": 500,
  "range": 5, "infantry": 25, "cavalry": 10, "archer": 25, "building": 90,
  "mystic": 25, "wallcrasher": 1,
  "gold": 500, "iron": 750, "wood": 2000, "food": 250
};
Units["u16"] = {
  "name": Translate("Shortbowman"), "pop": 1, "speed": 12, "carry": 25, "life": 15,
  "range": 3, "infantry": 15, "cavalry": 5, "archer": 15, "building": 20,
  "mystic": 15, "wallcrasher": 0,
  "gold": 15, "iron": 10, "wood": 20, "food": 10
};
Units["u17"] = {
  "name": Translate("Longbowman"), "pop": 1, "speed": 12, "carry": 15, "life": 40,
  "range": 4, "infantry": 25, "cavalry": 35, "archer": 15, "building": 20,
  "mystic": 15, "wallcrasher": 1,
  "gold": 50, "iron": 10, "wood": 70, "food": 20
};
Units["u18"] = {
  "name": Translate("Crossbowman"), "pop": 1, "speed": 14, "carry": 20, "life": 40,
  "range": 4, "infantry": 40, "cavalry": 30, "archer": 40, "building": 20,
  "mystic": 40, "wallcrasher": 1,
  "gold": 50, "iron": 25, "wood": 40, "food": 20
};
Units["u19"] = {
  "name": Translate("Cavalry Archer"), "pop": 5, "speed": 6, "carry": 60, "life": 130,
  "range": 4, "infantry": 100, "cavalry": 40, "archer": 100, "building": 70,
  "mystic": 100, "wallcrasher": 1,
  "gold": 200, "iron": 300, "wood": 250, "food": 150
};
Units["u20"] = {
  "name": Translate("Nobleman"), "pop": 100, "speed": 30, "carry": 100, "life": 300,
  "range": 1, "infantry": 30, "cavalry": 100, "archer": 30, "building": 10,
  "mystic": 30, "wallcrasher": 0,
  "gold": 44000, "iron": 44000, "wood": 48400, "food": 65535
};
Units["u21"] = {
  "name": Translate("High Priest"), "pop": 10, "speed": 15, "carry": 5, "life": 75,
  "range": 1, "infantry": 30, "cavalry": 30, "archer": 30, "building": 10,
  "mystic": 30, "wallcrasher": 0,
  "gold": 600, "iron": 100, "wood": 100, "food": 20
};
Units["u22"] = {
  "name": Translate("Airweaver"), "pop": 10, "speed": 15, "carry": 5, "life": 65,
  "range": 1, "infantry": 30, "cavalry": 30, "archer": 30, "building": 10,
  "mystic": 30, "wallcrasher": 0,
  "gold": 500, "iron": 50, "wood": 100, "food": 150
};
Units["u23"] = {
  "name": Translate("Elf Lord"), "pop": 10, "speed": 15, "carry": 10, "life": 80,
  "range": 1, "infantry": 30, "cavalry": 30, "archer": 30, "building": 10,
  "mystic": 30, "wallcrasher": 0,
  "gold": 650, "iron": 50, "wood": 50, "food": 200
};
Units["u24"] = {
  "name": Translate("Warlord"), "pop": 10, "speed": 8, "carry": 70, "life": 600,
  "range": 1, "infantry": 150, "cavalry": 125, "archer": 250, "building": 0,
  "mystic": 250, "wallcrasher": 0,
  "gold": 150, "iron": 300, "wood": 150, "food": 150
};
Units["u25"] = {
  "name": Translate("Grand Magus"), "pop": 10, "speed": 20, "carry": 100, "life": 400,
  "range": 1, "infantry": 150, "cavalry": 100, "archer": 150, "building": 0,
  "mystic": 100, "wallcrasher": 0,
  "gold": 100, "iron": 300, "wood": 700, "food": 300
};
Units["u26"] = {
  "name": "Samurai", "pop": 10, "speed": 5, "carry": 30, "life": 700,
  "range": 1, "infantry": 100, "cavalry": 100, "archer": 180, "building": 0,
  "mystic": 200, "wallcrasher": 0,
  "gold": 150, "iron": 250, "wood": 50, "food": 100
};
Units["u27"] = {
  "name": "KhanGuard", "pop": 10, "speed": 15, "carry": 80, "life": 400,
  "range": 1, "infantry": 150, "cavalry": 100, "archer": 200, "building": 0,
  "mystic": 250, "wallcrasher": 0,
  "gold": 100, "iron": 250, "wood": 100, "food": 300
};

var InfantryList = {"u1": null, "u2": null, "u3": null, "u4": null};
var ArcherList  = {"u16": null, "u17": null, "u18": null, "u19": null};
var CavalryList = {"u9": null, "u10": null, "u11": null};
var MysticList  = {"u12": null, "u13": null, "u14": null, "u15": null};
var RaceSpecific = {
  "u5": null, "u6": null, "u7": null, "u8": null,
  "u21": null, "u22": null, "u23": null, "u24": null, "u25": null,
  "u26": null, "u27": null
};
var Unupgraded = eval(RaceSpecific.toSource());
Unupgraded["u9"] = null; Unupgraded["u20"] = null;
var JunkList = {"u1": null, "u2": null, "u3": null, "u4": null, "u16": null};

var GuardLevels = [0, 200, 280, 380, 500, 666, 910, 1220, 1660, 2222, 3000];
var StoreLevels = [
  500, 1000, 1235, 1516, 1890, 2293, 2816, 3462, 4259, 5239, 6445, 7925, 9750,
  11990, 14750, 18150, 22315, 28000, 33799, 41535, 51045, 62821, 77289, 95042,
  116900, 143788, 176869, 217539, 267590, 323113, 400000
];

var Buildings = [
  [],
  [
    Translate("Gold Mine"),
    [30, 65, 40, 45, "00:02:30"],
    [47, 103, 63, 71, "00:03:58"],
    [60, 130, 80, 90, "00:05:00"],
    [75, 163, 100, 113, "00:06:18"],
    [95, 206, 127, 142, "00:07:56"],
    [120, 260, 160, 180, "00:10:00"],
    [151, 327, 201, 226, "00:12:36"],
    [190, 412, 254, 285, "00:15:52"],
    [240, 520, 320, 360, "00:20:00"],
    [302, 655, 403, 453, "00:25:10"],
    [381, 826, 508, 571, "00:31:46"],
    [480, 1040, 640, 720, "00:40:01"],
    [605, 1311, 807, 907, "00:50:26"],
    [762, 1652, 1016, 1143, "01:03:33"],
    [960, 2081, 1281, 1441, "01:20:04"],
    [1210, 2623, 1614, 1816, "01:40:53"],
    [1525, 3305, 2034, 2288, "02:07:07"],
    [1922, 4164, 2562, 2883, "02:40:10"],
    [2421, 5247, 3229, 3632, "03:21:49"],
    [3051, 6611, 4068, 4577, "04:14:18"],
    [3845, 8330, 5126, 5767, "05:20:25"],
    [4844, 10497, 6459, 7267, "06:43:43"],
    [6104, 13226, 8139, 9156, "08:28:42"],
    [7691, 16665, 10255, 11537, "10:40:57"],
    [9691, 20997, 12921, 14537, "13:27:36"],
    [12211, 26457, 16281, 18316, "16:57:35"],
    [15386, 33336, 20514, 23079, "21:22:10"],
    [19386, 42003, 25848, 29079, "26:55:31"],
    [24426, 52924, 32569, 36640, "33:55:34"],
    [30777, 66685, 41037, 46166, "42:44:49"]
  ],
  [
    Translate("Iron Mine"),
    [60, 35, 55, 45, "00:02:35"],
    [95, 55, 87, 71, "00:04:06"],
    [120, 70, 110, 90, "00:05:10"],
    [151, 88, 138, 113, "00:06:30"],
    [190, 111, 174, 142, "00:08:12"],
    [240, 140, 220, 180, "00:10:20"],
    [302, 176, 277, 226, "00:13:01"],
    [381, 222, 349, 285, "00:16:24"],
    [480, 280, 440, 360, "00:20:40"],
    [605, 352, 554, 453, "00:26:03"],
    [762, 444, 698, 571, "00:32:49"],
    [960, 560, 880, 720, "00:41:21"],
    [1210, 706, 1109, 907, "00:52:07"],
    [1525, 889, 1398, 1143, "01:05:40"],
    [1921, 1121, 1761, 1441, "01:22:44"],
    [2421, 1412, 2219, 1816, "01:44:15"],
    [3051, 1779, 2796, 2288, "02:11:21"],
    [3844, 2242, 3523, 2883, "02:45:31"],
    [4843, 2825, 4440, 3632, "03:28:33"],
    [6103, 3560, 5594, 4577, "04:22:46"],
    [7690, 4485, 7049, 5767, "05:31:06"],
    [9689, 5652, 8882, 7267, "06:57:11"],
    [12208, 7121, 11191, 9156, "08:45:39"],
    [15383, 8973, 14101, 11537, "11:02:19"],
    [19382, 11306, 17767, 14537, "13:54:32"],
    [24422, 14246, 22387, 18316, "17:31:30"],
    [30772, 17950, 28207, 23079, "22:04:54"],
    [38772, 22617, 35541, 29079, "27:49:22"],
    [48853, 28497, 44782, 36640, "35:03:25"],
    [61555, 35907, 56425, 46166, "44:10:18"]
  ],
  [
    Translate("Lumber Mill"),
    [65, 55, 35, 40, "00:02:33"],
    [103, 87, 55, 63, "00:04:02"],
    [130, 110, 70, 80, "00:05:06"],
    [163, 138, 88, 100, "00:06:25"],
    [206, 174, 111, 127, "00:08:05"],
    [260, 220, 140, 160, "00:10:12"],
    [327, 277, 176, 201, "00:12:51"],
    [412, 349, 222, 254, "00:16:11"],
    [520, 440, 280, 320, "00:20:24"],
    [655, 554, 352, 403, "00:25:43"],
    [826, 698, 444, 508, "00:32:24"],
    [1040, 880, 560, 640, "00:40:49"],
    [1311, 1109, 706, 807, "00:51:26"],
    [1652, 1398, 889, 1016, "01:04:49"],
    [2081, 1761, 1121, 1281, "01:21:40"],
    [2623, 2219, 1412, 1614, "01:42:54"],
    [3305, 2796, 1779, 2034, "02:09:40"],
    [4164, 3523, 2242, 2562, "02:43:23"],
    [5247, 4440, 2825, 3229, "03:25:51"],
    [6611, 5594, 3560, 4068, "04:19:23"],
    [8330, 7049, 4485, 5126, "05:26:49"],
    [10497, 8882, 5652, 6459, "06:51:48"],
    [13226, 11191, 7121, 8139, "08:38:52"],
    [16665, 14101, 8973, 10255, "10:53:46"],
    [20997, 17767, 11306, 12921, "13:43:45"],
    [26457, 22387, 14246, 16281, "17:17:56"],
    [33336, 28207, 17950, 20514, "21:47:48"],
    [42003, 35541, 22617, 25848, "27:27:50"],
    [52924, 44782, 28497, 32569, "34:36:16"],
    [100000, 56425, 35907, 41037, "43:36:06"]
  ],
  [
    Translate("Farm"),
    [55, 50, 35, 70, "00:02:33"],
    [87, 79, 55, 111, "00:04:02"],
    [110, 100, 70, 140, "00:05:06"],
    [138, 126, 88, 176, "00:06:25"],
    [174, 158, 111, 222, "00:08:05"],
    [220, 200, 140, 280, "00:10:12"],
    [277, 252, 176, 352, "00:12:51"],
    [349, 317, 222, 444, "00:16:11"],
    [440, 400, 280, 560, "00:20:24"],
    [554, 504, 352, 705, "00:25:43"],
    [698, 635, 444, 889, "00:32:24"],
    [880, 800, 560, 1120, "00:40:49"],
    [1109, 1008, 706, 1412, "00:51:26"],
    [1398, 1271, 889, 1779, "01:04:49"],
    [1761, 1601, 1121, 2242, "01:21:40"],
    [2219, 2017, 1412, 2825, "01:42:54"],
    [2796, 2542, 1779, 3559, "02:09:40"],
    [3523, 3203, 2242, 4485, "02:43:23"],
    [4440, 4036, 2825, 5651, "03:25:51"],
    [5594, 5086, 3560, 7120, "04:19:23"],
    [7049, 6408, 4485, 8971, "05:26:49"],
    [8882, 8074, 5652, 11304, "06:51:48"],
    [11191, 10174, 7121, 14243, "08:38:52"],
    [14101, 12819, 8973, 17946, "10:53:46"],
    [17767, 16152, 11306, 22613, "13:43:45"],
    [22387, 20351, 14246, 28492, "17:17:56"],
    [28207, 25643, 17950, 35900, "21:47:48"],
    [35541, 32310, 22617, 45234, "27:27:50"],
    [44782, 40711, 28497, 56995, "34:36:16"],
    [56425, 51296, 35907, 71814, "43:36:06"]
  ],
  [
    Translate("Homes"),
    [35, 30, 45, 40, "00:02:00"],
    [55, 47, 71, 63, "00:03:10"],
    [70, 60, 90, 80, "00:04:00"],
    [88, 75, 113, 100, "00:05:02"],
    [111, 95, 142, 127, "00:06:21"],
    [140, 120, 180, 160, "00:08:00"],
    [176, 151, 226, 201, "00:10:05"],
    [222, 190, 285, 254, "00:12:42"],
    [280, 240, 360, 320, "00:16:00"],
    [352, 302, 453, 403, "00:20:10"],
    [444, 381, 571, 508, "00:25:24"],
    [560, 480, 720, 640, "00:32:01"],
    [706, 605, 907, 807, "00:40:21"],
    [889, 762, 1143, 1016, "00:50:50"],
    [1121, 960, 1441, 1281, "01:04:03"],
    [1412, 1210, 1816, 1614, "01:20:42"],
    [1779, 1525, 2288, 2034, "01:41:42"],
    [2242, 1922, 2883, 2562, "02:08:08"],
    [2825, 2421, 3632, 3229, "02:41:27"],
    [3560, 3051, 4577, 4068, "03:23:26"],
    [4485, 3845, 5767, 5126, "04:16:20"],
    [5652, 4844, 7267, 6459, "05:22:59"],
    [7121, 6104, 9156, 8139, "06:46:57"],
    [8973, 7691, 11537, 10255, "08:32:46"],
    [11306, 9691, 14537, 12921, "10:46:05"],
    [14246, 12211, 18316, 16281, "13:34:04"],
    [17950, 15386, 23079, 20514, "17:05:44"],
    [22617, 19386, 29079, 25848, "21:32:25"],
    [28497, 24426, 36640, 32569, "27:08:27"],
    [35907, 30777, 46166, 41037, "34:11:51"]
  ],
  [
    Translate("Barracks"),
    [170, 90, 200, 100, "00:05:00"],
    [269, 142, 317, 158, "00:07:56"],
    [340, 180, 400, 200, "00:10:00"],
    [428, 226, 504, 252, "00:12:36"],
    [539, 285, 635, 317, "00:15:52"],
    [680, 360, 800, 400, "00:20:00"],
    [857, 453, 1008, 504, "00:25:12"],
    [1079, 571, 1270, 635, "00:31:45"],
    [1360, 720, 1600, 800, "00:40:01"],
    [1714, 907, 2017, 1008, "00:50:25"],
    [2160, 1143, 2541, 1270, "01:03:32"],
    [2722, 1441, 3202, 1601, "01:20:03"],
    [3429, 1815, 4035, 2017, "01:40:52"],
    [4321, 2287, 5084, 2542, "02:07:06"],
    [5445, 2882, 6406, 3203, "02:40:09"],
    [6860, 3632, 8071, 4035, "03:21:47"],
    [8644, 4576, 10170, 5085, "04:14:15"],
    [10892, 5766, 12814, 6407, "05:20:21"],
    [13724, 7265, 16146, 8073, "06:43:39"],
    [17292, 9154, 20344, 10172, "08:28:36"],
    [21788, 11535, 25633, 12816, "10:40:50"],
    [27453, 14534, 32298, 16149, "13:27:27"],
    [34591, 18313, 40696, 20348, "16:57:24"],
    [43585, 23074, 51277, 25638, "21:21:55"],
    [54917, 29074, 64609, 32304, "26:55:13"]
  ],
  [
    Translate("Stables"),
    [300, 245, 255, 235, "00:10:00"],
    [476, 388, 404, 373, "00:15:52"],
    [600, 490, 510, 470, "00:20:00"],
    [756, 617, 642, 592, "00:25:12"],
    [952, 778, 809, 746, "00:31:45"],
    [1200, 980, 1020, 940, "00:40:00"],
    [1512, 1235, 1285, 1184, "00:50:25"],
    [1905, 1556, 1619, 1492, "01:03:31"],
    [2401, 1961, 2041, 1881, "01:20:02"],
    [3025, 2470, 2571, 2370, "01:40:51"],
    [3812, 3113, 3240, 2986, "02:07:04"],
    [4803, 3922, 4083, 3762, "02:40:07"],
    [6052, 4942, 5144, 4741, "03:21:45"],
    [7626, 6228, 6482, 5973, "04:14:12"],
    [9609, 7847, 8167, 7527, "05:20:18"],
    [12107, 9887, 10291, 9484, "06:43:34"],
    [15255, 12458, 12966, 11949, "08:28:30"],
    [19221, 15697, 16338, 15056, "10:40:43"],
    [24219, 19779, 20586, 18971, "13:27:18"],
    [30516, 24921, 25938, 23904, "16:57:12"]
  ],
  [
    Translate("Guild"),
    [155, 265, 305, 235, "00:15:00"],
    [246, 420, 484, 373, "00:23:48"],
    [310, 530, 610, 470, "00:30:00"],
    [390, 667, 768, 592, "00:37:48"],
    [492, 841, 968, 746, "00:47:38"],
    [620, 1060, 1220, 940, "01:00:01"],
    [781, 1336, 1537, 1184, "01:15:37"],
    [984, 1683, 1937, 1492, "01:35:17"],
    [1240, 2121, 2441, 1881, "02:00:04"],
    [1563, 2672, 3076, 2370, "02:31:17"],
    [1969, 3367, 3875, 2986, "03:10:37"],
    [2481, 4243, 4883, 3762, "04:00:10"],
    [3127, 5346, 6153, 4741, "05:02:37"],
    [3940, 6736, 7753, 5973, "06:21:18"],
    [4964, 8487, 9769, 7527, "08:00:27"]
  ],
  [
    Translate("Market"),
    [105, 95, 105, 95, "00:06:00"],
    [166, 150, 166, 150, "00:09:31"],
    [210, 190, 210, 190, "00:12:00"],
    [264, 239, 264, 239, "00:15:07"],
    [333, 301, 333, 301, "00:19:03"],
    [420, 380, 420, 380, "00:24:00"],
    [529, 478, 529, 478, "00:30:15"],
    [667, 603, 667, 603, "00:38:07"],
    [840, 760, 840, 760, "00:48:01"],
    [1058, 958, 1058, 958, "01:00:30"],
    [1334, 1207, 1334, 1207, "01:16:14"],
    [1681, 1521, 1681, 1521, "01:36:04"],
    [2118, 1916, 2118, 1916, "02:01:03"],
    [2669, 2414, 2669, 2414, "02:32:31"],
    [3363, 3042, 3363, 3042, "03:12:10"],
    [4237, 3834, 4237, 3834, "04:02:08"],
    [5339, 4830, 5339, 4830, "05:05:06"],
    [6727, 6086, 6727, 6086, "06:24:26"],
    [8476, 7669, 8476, 7669, "08:04:23"],
    [10680, 9663, 10680, 9663, "10:10:19"],
    [13457, 12176, 13457, 12176, "12:49:00"],
    [16956, 15341, 16956, 15341, "16:08:57"],
    [21365, 19330, 21365, 19330, "20:20:52"],
    [26920, 24356, 26920, 24356, "25:38:18"],
    [33919, 30689, 33919, 30689, "32:18:16"]
  ],
  [
    Translate("Training Grounds"),
    [230, 245, 225, 185, "00:20:00"],
    [365, 388, 357, 293, "00:31:45"],
    [460, 490, 450, 370, "00:40:00"],
    [579, 617, 567, 466, "00:50:24"],
    [730, 778, 714, 587, "01:03:30"],
    [920, 980, 900, 740, "01:20:01"],
    [1159, 1235, 1134, 932, "01:40:50"],
    [1461, 1556, 1429, 1175, "02:07:03"],
    [1841, 1961, 1801, 1480, "02:40:05"],
    [2319, 2470, 2269, 1865, "03:21:42"],
    [2922, 3113, 2859, 2350, "04:14:09"],
    [3682, 3922, 3602, 2962, "05:20:14"],
    [4640, 4942, 4539, 3732, "06:43:30"],
    [5846, 6228, 5719, 4702, "08:28:24"],
    [7366, 7847, 7206, 5925, "10:40:36"],
    [9282, 9887, 9080, 7466, "13:27:09"],
    [11695, 12458, 11441, 9407, "16:57:01"],
    [14736, 15697, 14416, 11853, "21:21:26"],
    [18568, 19779, 18164, 14935, "26:54:37"],
    [23395, 24921, 22887, 18818, "33:54:25"]
  ],
  [
    Translate("Hospital"),
    [305, 245, 225, 315, "00:25:00"],
    [484, 388, 357, 500, "00:39:41"],
    [610, 490, 450, 630, "00:50:00"],
    [768, 617, 567, 793, "01:03:00"],
    [968, 778, 714, 1000, "01:19:23"],
    [1220, 980, 900, 1260, "01:40:02"],
    [1537, 1235, 1134, 1588, "02:06:02"],
    [1937, 1556, 1429, 2001, "02:38:49"],
    [2441, 1961, 1801, 2521, "03:20:06"],
    [3076, 2470, 2269, 3176, "04:12:08"]
  ],
  [
    Translate("Wall"),
    [35, 50, 100, 20, "00:06:00"],
    [55, 79, 158, 31, "00:09:31"],
    [70, 100, 200, 40, "00:12:00"],
    [88, 126, 252, 50, "00:15:07"],
    [111, 158, 317, 63, "00:19:03"],
    [140, 200, 400, 80, "00:24:00"],
    [176, 252, 504, 100, "00:30:15"],
    [222, 317, 635, 127, "00:38:07"],
    [280, 400, 800, 160, "00:48:01"],
    [352, 504, 1008, 201, "01:00:30"],
    [1332, 1905, 3810, 762, "02:62:28"],
    [1680, 2400, 4803, 960, "03:12:04"],
    [2118, 3024, 6051, 1209, "04:02:06"],
    [2667, 3813, 7626, 1524, "05:05:02"],
    [3363, 4803, 9609, 1920, "06:24:20"],
    [7060, 10085, 20175, 4035, "12:06:24"],
    [8895, 12710, 25425, 5085, "15:15:18"],
    [11210, 16015, 32035, 6405, "19:13:18"],
    [14125, 20180, 40365, 8070, "24:13:09"],
    [17800, 25430, 50860, 10170, "30:30:57"]
  ],
  [
    Translate("Order"),
    [55000, 26400, 38500, 55000, "24:00:00"],
    [88000, 44000, 60500, 88000, "38:06:08"],
    [220000, 110000, 99000, 165000, "48:00:32"]
  ],
  [
    Translate("Guard Station"),
    [45, 55, 45, 35, "00:10:00"],
    [71, 87, 71, 55, "00:15:52"],
    [90, 110, 90, 70, "00:20:00"],
    [113, 138, 113, 88, "00:25:12"],
    [142, 174, 142, 111, "00:31:45"],
    [180, 220, 180, 140, "00:40:00"],
    [226, 277, 226, 176, "00:50:25"],
    [285, 349, 285, 222, "01:03:31"],
    [360, 440, 360, 280, "01:20:02"],
    [453, 554, 453, 352, "01:40:51"]
  ],
  [
    Translate("Store House"),
    [47, 62, 45, 52, "00:07:24"],
    [74, 98, 71, 39, "00:11:44"],
    [94, 124, 90, 50, "00:14:48"],
    [118, 156, 113, 63, "00:18:39"],
    [149, 196, 142, 79, "00:23:30"],
    [188, 248, 180, 100, "00:29:36"],
    [236, 312, 226, 126, "00:37:18"],
    [298, 393, 285, 158, "00:47:00"],
    [376, 496, 360, 200, "00:59:14"],
    [474, 625, 453, 252, "01:14:38"],
    [597, 787, 571, 317, "01:34:02"],
    [752, 992, 720, 400, "01:58:29"],
    [948, 1250, 907, 504, "02:29:17"],
    [1194, 1576, 1143, 635, "03:08:06"],
    [1505, 1985, 1441, 800, "03:57:01"],
    [1896, 2502, 1816, 1008, "04:58:38"],
    [2389, 3152, 2288, 1271, "06:16:17"],
    [3011, 3972, 2883, 1601, "07:54:08"],
    [3794, 5005, 3632, 2018, "09:57:24"],
    [4780, 6306, 4577, 2543, "12:32:44"],
    [6023, 7946, 5767, 3204, "15:48:26"],
    [7590, 10012, 7267, 4037, "19:55:02"],
    [9563, 12615, 9156, 5087, "25:05:45"],
    [12050, 15895, 11537, 6409, "31:37:15"],
    [15183, 20028, 14537, 8076, "39:50:32"],
    [19130, 25236, 18316, 10175, "50:12:04"],
    [24104, 31797, 23079, 12821, "63:15:12"],
    [30371, 40065, 29079, 16155, "79:41:58"],
    [38268, 50482, 36640, 20355, "100:25:17"],
    [48218, 63607, 46166, 25648, "126:31:51"]
  ]
];

var RoadBonus = 0.15;
var CancelUnit = 0.9;
var ResList = [["Gold", "g"], ["Iron", "j"], ["Wood", "d"], ["Food", "h"]];

/******************************************************************************/
// page/user constants

var UKMode = (Host.indexOf("utopiakingdoms.com") > -1);
var HomeName = /(.+) \((\d+):(\d+)\)/.exec(
  document.getElementById("changeCastle").
    getElementsByTagName("big")[0].innerHTML
);
var Home = HomeName.slice(2);
HomeName = HomeName[1];

// global resources
var GlobalGold = Number(document.getElementById("HaveGold").innerHTML);
var GlobalIron = Number(document.getElementById("HaveIron").innerHTML);
var GlobalWood = Number(document.getElementById("HaveWood").innerHTML);
var GlobalFood = Number(document.getElementById("HaveFood").innerHTML);
var GlobalTotal = GlobalGold + GlobalIron + GlobalWood + GlobalFood;

var World = /(\d+)\s*—/i.exec(document.title);
World ? World = World[1] : World = "0";
var UserName = document.getElementById("user").value;
var SavedHost = /[^.]+?\.(.+)$/i.exec(window.location.hostname);
SavedHost = (SavedHost ? SavedHost[1] : "") + "_" + World;
var SavePrefix = SavedHost + "_" + UserName;

var PlayerCastles =
  eval(GM_getValue(SavePrefix + "Castles")) ||
    [[].concat([null, HomeName], Home)];
var PlayerMerchants =
  eval(GM_getValue(SavePrefix + "Merchants")) || [];
var UnitNameToID = [];
var UnitMaxID = [];
for (var N in Units) {
  UnitNameToID[Units[N].name] = N; UnitMaxID.push(Number(N.slice(1)));
}
UnitMaxID.sort(CompNum).reverse();
UnitMaxID = UnitMaxID[0];

var BuildingMaxID = [];
for (var N = 1; N < Buildings.length; N++) {
  BuildingMaxID.push(N);
}
BuildingMaxID.sort(CompNum).reverse();
BuildingMaxID = BuildingMaxID[0];

var DefaultAttack = GM_getValue(SavePrefix + "DefaultAttack", "");
var ArcherLevel = Number(GM_getValue(SavePrefix + "Archer", 0));
var MysticLevel = Number(GM_getValue(SavePrefix + "Mystic", 0));
var ScoutLevel = Number(GM_getValue(SavePrefix + "Scout", 0));
var MerchantLevel = Number(GM_getValue(SavePrefix + "Merchant", 0));
var CavalryLevel = Number(GM_getValue(SavePrefix + "Cavalry", 0));
var WarriorLevel = Number(GM_getValue(SavePrefix + "Warrior", 0));
var DefenderLevel = Number(GM_getValue(SavePrefix + "Defender", 0));
var StealFrac = Number(GM_getValue(SavePrefix + "StealFrac", 0));
var HealThreshold = Number(GM_getValue(SavePrefix + "MinHeal", 1));
var ScavengeThreshold =
  Number(GM_getValue(SavePrefix + "MinScavenge", 1));
var TimeOffset =
  Number(GM_getValue(SavePrefix + "ServerOffset", 0));
var MaxX = Number(GM_getValue(SavePrefix + "MaxX", 200));
var MaxY = Number(GM_getValue(SavePrefix + "MaxY", 200));

// UnitUpgrades["id"] = [PctAttackPerLevel, PctDefPerLevel]
var UnitUpgrades = [];
for (var Unit in Units) {
  var Bonus = 0;
  if (! (Unit in Unupgraded)) {
    if (Unit in CavalryList) {
      Bonus = 10;
    } else if (Unit in MysticList) {
      Bonus = 30;
    } else if (Unit in InfantryList || Unit in ArcherList) {
      Bonus = 5;
    }
  }
  if (Bonus)
    UnitUpgrades[Unit] = [
      Number(GM_getValue(SavePrefix + Unit + "a", 0)), Bonus,
      Number(GM_getValue(SavePrefix + Unit + "d", 0)), 10
    ];
}

/******************************************************************************/
// helpers

// translation extraction
function Translate(Word) {
  if (Lang in Translation) {
    if (Word in Translation[Lang]) {
      return Translation[Lang][Word];
    } else {
      return Word;
    }
  } else {
    return Word;
  }
}

// add/remove class to a node
function AddClass(Node, Class) {
  var CurrClass = Node.getAttribute("class") || "";
  Node.setAttribute("class", CurrClass + (CurrClass ? " " : "") + Class);
}
function RemoveClass(Node, Class) {
  var CurrClass = Node.getAttribute("class") || "";
  CurrClass = CurrClass.replace(new RegExp("\\s*" + Class + "\\s*", "i"), " ");
  Node.setAttribute("class", CurrClass);
}

// for use with REs in extracting castle/player names for bare coords
function GetCastleName(arguments, ID) {
  var Coords = [arguments[1], arguments[2]];
  var FoundCastle = [[], []];
  for (var N = 0; N < PlayerCastles.length; N++) {
    if (
      PlayerCastles[N][2] == Coords[0] &&
      PlayerCastles[N][3] == Coords[1]
    ) {
      FoundCastle[0] = PlayerCastles[N][1]; break;
    }
  }
  if (! FoundCastle[0].length) {
    if (ID == undefined) {
      FoundCastle[1] = eval(GM_getValue(
        SavedHost + "_MapData_" + Coords[0] + "_" + Coords[1],
        "[]"
      ));
      if (FoundCastle[1][2]) {
        FoundCastle[1] = [FoundCastle[1][2], FoundCastle[1][4]];
      } else {
        FoundCastle[1] = [];
      }
      return FoundCastle;
    } else {
      window.setTimeout(
        function () {
          FoundCastle[1] = eval(GM_getValue(
            SavedHost + "_MapData_" + Coords[0] + "_" + Coords[1],
            "[]"
          ));
          if (FoundCastle[1][2]) {
            FoundCastle[1] = [FoundCastle[1][2], FoundCastle[1][4]];
          } else {
            FoundCastle[1] = [];
          }
          document.getElementById(ID).innerHTML=
              (FoundCastle[0].length
               ? " (" + FoundCastle[0] + ")"
               : (FoundCastle[1].length ? " (" + FoundCastle[1].join(", ") + ")" : ""));
        },
        0
      );
    }
  } else {
    if (ID == undefined) {
      return FoundCastle;
    } else {
      document.getElementById(ID).innerHTML=
          (FoundCastle[0].length
           ? " (" + FoundCastle[0] + ")"
           : (FoundCastle[1].length ? " (" + FoundCastle[1].join(", ") + ")" : ""));
    }
  }
}

// newline/<br>-separated unit parsing--copy directly to about:config
function ParseClipboardUnits(InText) {
  if (InText.indexOf("<br>") == -1) {
    var Units = InText.split("\n");
  } else {
    Units = InText.split("<br>");
  }
  var ArmyArray = new Array(UnitMaxID + 1);
  var PatSplit = /^\s*(.+?)\s*:\s*(\d+)\s*$/i;
  for (var N = 0; N < Units.length; N++) {
    var Match = PatSplit.exec(Units[N]);
    if (Match) {
      var ID = UnitNameToID[Match[1]];
      if (ID) ArmyArray[Number(ID.slice(1))] = Number(Match[2]);
    }
  }
  GM_setValue(SavePrefix + "_CopyArmy", uneval(ArmyArray));
}

// attach local time
function RefreshClock() {
  var Now = new Date();
  document.getElementById("GM_Clock").innerHTML =
    "" + Zero(String(Now.getHours()), 2) +
    ":" + Zero(String(Now.getMinutes()), 2) + ":" +
    Zero(String(Now.getSeconds()), 2) + " (";
  Now.setMinutes(Now.getMinutes() - TimeOffset);
  document.getElementById("GM_Clock").innerHTML +=
    Zero(String(Now.getHours()), 2) +
    ":" + Zero(String(Now.getMinutes()), 2) + ":" +
    Zero(String(Now.getSeconds()), 2) + ")";
}

var InitOffset = 0;
var Node = document.getElementById("serverTime");
if (Node) {
  var Now = new Date();
  var PageMatch = new RegExp(
    Host + "/pohod\\.php(?:\\?.*attackx=(\\d+)&attacky=(\\d+))?",
    "i"
  ).test(window.location);
  var Formation = document.getElementById("army_orders");
  if (PageMatch && ! Formation) {
    var Form = document.forms.namedItem("formClock").elements;
  }

  var LocalSeconds =
    Now.getHours() * 3600 + Now.getMinutes() * 60 + Now.getSeconds()
  var ServerTime = /<strong>(\d+):(\d+):(\d+)<\/strong>/i.exec(Node.innerHTML);
  // add full day to get rid of looping problems
  var ServerHours = Number(ServerTime[1]) + 24;
  var ServerSeconds = ServerHours * 3600 +
    Number(ServerTime[2]) * 60 + Number(ServerTime[3])
  InitOffset = -((ServerSeconds - LocalSeconds) % 86400) / 60;
  Node.innerHTML = "<strong id='GM_Clock'></strong>";
  Node.style.textAlign = "left";
  RefreshClock();
  window.setInterval(RefreshClock, 1000);
}

var PatDate = new RegExp(
  "(?:" + Translate("Mo") + "|" + Translate("Tu") + "|" + Translate("We") +
    "|" + Translate("Th") + "|" + Translate("Fr") + "|" + Translate("Sa") +
    "|" + Translate("Su") + ")?(\\d{4,4})-(\\d\\d)-(\\d\\d)(?:\\s*|@|<br />)" +
    "(\\d\\d):(\\d\\d):(\\d\\d)",
  "i"
);

// release troops from all training facilities
function ReleaseTroops() {
  function CheckDone() {
    Attempts += 1;
    var Sorted = eval(Done.toSource());
    Sorted.sort(CompNum);
    if (Sorted[0] == 1 || Attempts >= 20) {
      window.clearInterval(Interval);
    }
    window.location.reload();
  }

  function DoRequest(URL, Index) {
    GM_xmlhttpRequest({
      method: "GET",
      url: URL,
      onload: function (Response) {
        Done[Index] = 1;
      }
    });
  }

  var Done = [0, 0, 0, 0];
  for (var N = 1; N <= 4; N++) {
    DoRequest(Host + "/barracks.php?unit_type=" + String(N), N);
  }
  var Attempts = 0;
  var Interval = window.setInterval(CheckDone , 500);
}

var InsertionPoint = document.getElementById("content");
if (InsertionPoint) {
  var LinkNode = document.createElement("div");
  LinkNode.id = "GM_UsefulLinks";
  LinkNode.style.textAlign = "center";
  LinkNode.innerHTML =
    "<a href='market.php?action=4'>" + Translate("Convert Res") + "</a> | " +
    "<a href='market.php?action=2'>" + Translate("Send Res") + "</a> | " +
    "<a href='vip_status.php'>" + Translate("Overview") + "</a> | " +
    "<a href='forum.php'>" + Translate("Clan Forum") + "</a> | " +
    "<a href='editclan.php?id=" +
      GM_getValue(SavePrefix + "ClanID") + "'>" +
      Translate("Clan Admin") + "</a> | " +
    "<a href='vip_search.php'>" + Translate("VIP Search") + "</a>";
  InsertionPoint.insertBefore(LinkNode, InsertionPoint.firstChild);
}

var InsertionPoint = document.getElementsByClassName("barracks")[0];
if (InsertionPoint) {
  var LinkNode = document.createElement("li");
  LinkNode.setAttribute("class", "marches");
  LinkNode.innerHTML =
    "<a href='javascript:void(0);' id='GM_ReleaseTroops'>" +
    Translate("Release Troops") + "</a>";
  InsertionPoint.parentNode.insertBefore(LinkNode, InsertionPoint);
  document.getElementById("GM_ReleaseTroops").
    addEventListener("click", ReleaseTroops, true);
}

// convert all dates
var DateNodes = document.evaluate(
  "//text()", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
);
// if march info page, do not overwrite, but append parenthesised server
var MarchInfo = new RegExp(
  Host + "/info\\.php\\?what=pohod&posted_id=\\d+$", "i"
).test(window.location);
for (var N = 0; N < DateNodes.snapshotLength; N++) {
  var Match = PatDate.exec(DateNodes.snapshotItem(N).data);
  if (Match) {
    DateNodes.snapshotItem(N).data =
      DateNodes.snapshotItem(N).data.replace(
      PatDate,
      function () {
        return DateFormat(LocalTime(arguments[0], TimeOffset)) +
          (MarchInfo ? " (" + Translate("Server") + " " + Match[0] + ")" : "");
      }
    );
  }
}

// return index of first occurrence of Item in InArr or -1 if not present
function ArrMember(Item, InArr) {
  for (var N = 0; N < InArr.length; N++) {
    if (Item == InArr[N]) return N;
  }
  return -1;
}

// round to nearest arbitrary unit
function Round(In, Unit) {
  return Math.round(In / Unit) * Unit;
}

// floating-point errors screw up rounding; truncate to specified decimal places
function StripDecimals(InNum, Places) {
  var PatDec = new RegExp(
    "(\\-?\\d+)" + (Places > 0 ? "(\\.\\d{1," + String(Places) +
      "})?(?:\\d*)?" : ""),
    "i"
  );
  var Match = PatDec.exec(String(InNum));
  return Match ? Match[1] + (Places > 0 ? Match[2] || "" : "") : "0";
}

// compare function for sorting array numbers
function CompNum(a, b) {return a - b;}

// get a unique array of speeds by descending order
function CalcSpeeds(ScoutLevel) {
  var Speeds = [];
  for (Unit in Units) {
    var Current = Units[Unit]["speed"];
    var Effective = Math.round(Current / (1 + ScoutLevels[ScoutLevel] / 100))
    if (Speeds.length == 0) {
      Speeds.push([Effective, [Current]]);
    } else {
      var Found = false;
      for (var N = 0; N < Speeds.length; N++) {
        if (Speeds[N][0] == Effective) {
          for (var M = 0; M < Speeds[N][1].length; M++) {
            if (Speeds[N][1][M] == Current) {
              Found = true; break;
            }
          }
          if (! Found) {
            Speeds[N][1].push(Current);
            Speeds[N][1].sort(CompNum);
            Found = true;
          }
        } else {
          if (N == Speeds.length - 1 && ! Found) {
            Speeds.push([Effective, [Current]]); break;
          }
        }
      }
    }
  }
  Speeds.sort(function (a, b) {return CompNum(a[0], b[0])}).reverse();
  return Speeds;
}
Speeds = CalcSpeeds(ScoutLevel);

// pad string with leading zeroes
function Zero(InStr, Length) {
  if (InStr.length < Length) {
    var Pad = "";
    for (var N = 0; N < Length - InStr.length; N++) {Pad += "0";}
    return Pad + InStr;
  } else {
    return InStr;
  }
}

// sum of array components
function ArraySum(InArr) {
  var Sum = 0;
  for (var N = 0; N < InArr.length; N++) {
    Sum += Number(InArr[N]);
  }
  return Sum;
}

// component-wise sum of two arrays, to shortest array
function TwoArraySum(Arr1, Arr2) {
  var Max = Math.min(Arr1.length, Arr2.length);
  var OutArr = new Array(Max);
  for (var N = 0; N < Max; N++) {
    OutArr[N] = Arr1[N] + Arr2[N];
  }
  return OutArr;
}

// cartesian distance from home coords
function Dist(TargetCoords, BaseCoords, IgnoreRoadBonus) {
  if (! BaseCoords) BaseCoords = [Home[0], Home[1]];
  if (! IgnoreRoadBonus) IgnoreRoadBonus = false;
  var RoadMod =  1;
  if (! IgnoreRoadBonus) {
    var TargetFound = BaseFound = false;
    for (var N = 0; N < PlayerCastles.length; N++) {
      if (
        TargetCoords[0] == PlayerCastles[N][2] &&
        TargetCoords[1] == PlayerCastles[N][3]
      ) {
        TargetFound = true; break;
      }
    }
    for (var N = 0; N < PlayerCastles.length; N++) {
      if (
        BaseCoords[0] == PlayerCastles[N][2] &&
        BaseCoords[1] == PlayerCastles[N][3]
      ) {
        BaseFound = true; break;
      }
    }
    // handle road bonus
    var QuickNode = document.getElementById("GM_QuickRoads");
    var QuickRoads = false;
    if (
      BaseFound && TargetFound && QuickNode &&
      ! /TrapMapUpdate/i.test(arguments.callee.caller) &&
      ! /LookForoDv/i.test(arguments.callee.caller)
    ) {
      QuickNode.checked = true;
    }
    if (QuickNode && QuickNode.checked) QuickRoads = true;
    if ((BaseFound && TargetFound) || QuickRoads) RoadMod = 1 - RoadBonus;
  }
  return RoadMod * Math.max(
    Math.sqrt(
      Math.pow(Number(BaseCoords[0]) - Number(TargetCoords[0]), 2) +
        Math.pow(Number(BaseCoords[1]) - Number(TargetCoords[1]), 2)
    ),
    1
  );
}

// convert hh:mm:ss to seconds
function ToSeconds(InStr) {
  var Match = /(\d+):(\d+):(\d+)/i.exec(InStr);
  return Number(Match[1]) * 3600 + Number(Match[2]) * 60 + Number(Match[3]);
}

// convert number of seconds to hms format
function HMS(Seconds) {
  var Hours = Math.max(Math.floor(Seconds / 3600), 0);
  Seconds = Seconds - Hours * 3600;
  var Minutes = Math.max(Math.floor(Seconds / 60), 0);
  Seconds = Math.ceil(Math.max(Seconds - Minutes * 60, 0));
  return String(Hours) + ":" +
    Zero(String(Minutes), 2) + ":" + Zero(String(Seconds), 2);
}

// convert DATE object into DDyyyy-mm-dd@hh:mm:ss format
function DateFormat(NewDate, Break) {
  return [
    Translate("Su"), Translate("Mo"), Translate("Tu"), Translate("We"),
    Translate("Th"), Translate("Fr"), Translate("Sa")
  ][NewDate.getDay()] +
    String(NewDate.getFullYear()) + "-" +
    Zero(String(NewDate.getMonth() + 1), 2) +
    "-" + Zero(String(NewDate.getDate()), 2) + (Break ? "<br />" : "@") +
    Zero(String(NewDate.getHours()), 2) + ":" +
    Zero(String(NewDate.getMinutes()), 2) + ":" +
    Zero(String(NewDate.getSeconds()), 2)
  ;
}

// calculate one-way and finish time for marches
function MarchTime(
  Dist, Speed, Separator, SpeedLimiter, ShowServer, KhanGuard, DisplaySpeed
) {
  if (! Separator) Separator = " ";
  if (! SpeedLimiter) {
    var SpeedLimiter = [];
    for (var N = 0; N < 10; N++) {SpeedLimiter.push(N);}
  }
  if (! DisplaySpeed) DisplaySpeed = String(Speed);
  var DateVar = new Date();
  var NewDate = new Date();
  var OneWay = new Date();
  var OneWayServer = new Date();
  var TimeString = "";
  if (KhanGuard) {
    KhanGuard = document.evaluate(
      "//input[@name='units[27]']",
      document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
    ).singleNodeValue;
    // KG only has a meaningful effect if marching with units as slow as it
    if (
      KhanGuard &&
      Speed >=
        Math.round(Units["u27"]["speed"] / (1 + ScoutLevels[ScoutLevel] / 100))
      ) {
      KhanGuard =
        1 - Math.max(Math.min(Number(KhanGuard.value) || 0, 60), 0) * 0.005;
    } else {
      KhanGuard = 1;
    }
  } else {
    KhanGuard = 1;
  }
  for (var N = 0; N < SpeedLimiter.length; N++) {
    var Seconds =
      KhanGuard * Math.ceil(60 * (1 + SpeedLimiter[N] / 10) * Speed * Dist);
    NewDate.setTime(DateVar.getTime() + 2000 * Seconds);
    OneWay.setTime(DateVar.getTime() + 1000 * Seconds);
    OneWayServer.setTime(OneWay.getTime());
    OneWayServer.setTime(OneWayServer.getTime() - 60000 * TimeOffset);
    TimeString += "<span class='hovertext' title='" +
      Translate("One-Way") + " (" + String(N) +
      "): " + DateFormat(OneWay) + "'>" + HMS(Seconds) + "," + Separator +
      DateFormat(NewDate) + "</span>" + Separator + "(" + DisplaySpeed +
      (KhanGuard < 1 ? "<b>*</b>" : "") + "-" + String(SpeedLimiter[N]) + ")" +
      (ShowServer
        ? "<br /><span style='font-weight: lighter;'>" +
          Translate("<i>Estimated</i> Server Arrival") + ": " +
          DateFormat(OneWayServer) + "</span>"
        : ""
      ) + "<br />";
  }
  return TimeString;
}

// convert server timestamp to local timestamp
function LocalTime(ServerTime, Offset) {
  if (! Offset) Offset = 0;
  var Match = PatDate.exec(ServerTime);
  var LocalTime = new Date(
    Number(Match[1]), Number(Match[2]) - 1, Number(Match[3]),
    Number(Match[4]), Number(Match[5]) + Offset, Number(Match[6])
  );
  return LocalTime;
}

// returns strings of castle distances for given target coords
function CalcCastleDists(Target, DoSpeed, ShowServer, KhanGuard) {
  var OutArray = [];
  var OutText = OutTabText = "";
  var Found = false;
  for (var M = 0; M < PlayerCastles.length; M++) {
    OutArray.push([
      PlayerCastles[M][1],
      Dist(Target, [PlayerCastles[M][2], PlayerCastles[M][3]]),
      PlayerCastles[M][2], PlayerCastles[M][3]
    ]);
    // handle camp, if current active settlement
    if (
      ! Found && Home[0] == PlayerCastles[M][2] && Home[1] == PlayerCastles[M][3]
    ) Found = true;
  }
  if (! Found) {
    OutArray.push([
      HomeName + " (" + Home[0] + ":" + Home[1] + ")",
      Dist(Target, Home), Home[0], Home[1]
    ]);
  }
  var Unsorted = [];
  for (var M = 0; M < OutArray.length; M++) {Unsorted.push(OutArray[M]);}
  OutArray.sort(function (a, b) {return CompNum(a[1], b[1]);});
  for (var M = 0; M < OutArray.length; M++) {
    var CurrentCastle = (Home[0] == OutArray[M][2] && Home[1] == OutArray[M][3]);
    OutText +=
      (CurrentCastle ? "<b><i>" : "") +
      OutArray[M][0] + ", " +
      (DoSpeed
        ? MarchTime(OutArray[M][1], DoSpeed, " ", [0], ShowServer, KhanGuard)
        : StripDecimals(Round(OutArray[M][1], 0.01), 2) + "<br />"
      ) + (CurrentCastle ? "</i></b>" : "");
    OutTabText += (OutTabText != "" ? "\t" : "") +
      StripDecimals(Unsorted[M][1], 2);
  }
  return [
    OutText, OutTabText,
    OutArray[0][0] + " " + StripDecimals(OutArray[0][1], 2)
  ];
}

// remove selected HREF search param from a search string
function CleanSearch(Search, Remove, Add) {
  var PatRemove = new RegExp("(\\?|&)" + Remove + "=[^&]+", "i");
  var NewSearch = Search.replace(PatRemove, "");
  if (NewSearch) NewSearch = "?" + NewSearch.slice(1);
  if (NewSearch != "?" && NewSearch != "" && Add) NewSearch += "&";
  NewSearch += (NewSearch == "" ? "?" : "") + Add;
  return NewSearch;
}

function MerchantCapacity() {
  var NewMessage = document.createElement("div");
  NewMessage.style.textAlign = "center"; NewMessage.style.color = "white";
  NewMessage.id = "GM_GlobalRes";
  NewMessage.innerHTML = Translate("Total Res") + ": " + String(GlobalTotal);
  for (var N = 0; N < PlayerMerchants.length; N++) {
    if (Home[0] == PlayerMerchants[N][0] && Home[1] == PlayerMerchants[N][1]) {
      var MerchantLimit = Number(PlayerMerchants[N][2]);
      var Extra = Math.max(0, GlobalTotal - MerchantLimit);
      NewMessage.innerHTML += "; " + Translate("Max Merchant") + ": " +
      String(MerchantLimit) + "; " + Translate("Extra") + ": " + String(Extra);
      break;
    }
  }
  var InsertionPoint = document.getElementById("GM_GlobalRes");
  if (! InsertionPoint) {
    InsertionPoint = document.getElementById("GM_UsefulLinks");
    InsertionPoint.parentNode.insertBefore(NewMessage, InsertionPoint);
  } else {
    InsertionPoint.innerHTML = NewMessage.innerHTML;
  }
}
MerchantCapacity();

var TrapCastleUpdateArray = [];
function TrapCastleUpdate(Event) {
  var Castles = document.getElementById("castlesList");
  var Nodes = document.evaluate(
    "descendant::big",
    Event.target, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  var PatCastle = /(.+)\s*?\((\d+):(\d+)\)/i;
  var PatCastleID = /\?tpid=(\d+)/i;
  var MarchPage = /pohod\.php/.test(window.location);
  if (MarchPage) {
    var Forms = document.forms.namedItem("form1").elements;
    var X = Forms.namedItem("attackx").value;
    var Y = Forms.namedItem("attacky").value;
    var PatX = /attackx=\d+/i;
  }
  for (var N = 0; N < Nodes.snapshotLength; N++) {
    var Match = PatCastle.exec(Nodes.snapshotItem(N).innerHTML);
    if (Match) {
      var CastleID = PatCastleID.exec(Nodes.snapshotItem(N).parentNode.href)[1];
      Match.push(CastleID);
      TrapCastleUpdateArray.push(Match);
    }
    if (MarchPage) {
      var Link = document.evaluate(
        "descendant::a",
        Event.target, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
      ).singleNodeValue;
      var LinkLocation = Link.href;
      if (! PatX.test(LinkLocation)) {
        var Pos = LinkLocation.indexOf("&location=") + 10;
        var Redir = unescape(LinkLocation.slice(Pos));
        var RedirSearchPos = Redir.indexOf("?");
        var RedirSearch;
        if (RedirSearchPos > -1) {
          RedirSearch = Redir.slice(RedirSearchPos);
          RedirSearch = CleanSearch(
            CleanSearch(RedirSearch, "attackx", ""),
            "attacky",
            "attackx=" + X + "&attacky=" + Y
          );
          RedirSearch =
            CleanSearch(CleanSearch(RedirSearch, "motivated", ""), "sent", "");
          Link.href = LinkLocation.slice(0, Pos) +
            escape(Redir.slice(0, RedirSearchPos) + RedirSearch);
        }
      }
    }
  }
  window.setTimeout(
    function (TrapCastleUpdateArray) {
      GM_setValue(
        SavePrefix + "Castles", uneval(TrapCastleUpdateArray)
      );
      PlayerCastles = eval(TrapCastleUpdateArray.toSource());
    },
    0,
    TrapCastleUpdateArray
  );
}

// listen for castle list check
document.getElementById("castlesList").
  addEventListener("DOMNodeInserted", TrapCastleUpdate, true);

// system clipboard access
unsafeWindow.GetClipboard = function () {
  this.netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
  var Clip = Components.classes["@mozilla.org/widget/clipboard;1"].
    getService(Components.interfaces.nsIClipboard);
  if (! Clip) return false;
  var Trans = Components.classes["@mozilla.org/widget/transferable;1"].
    createInstance(Components.interfaces.nsITransferable);
  if (! Trans) return false;
  Trans.addDataFlavor("text/unicode");
  Clip.getData(Trans, Clip.kGlobalClipboard);
  var Str = new Object();
  var StrLength = new Object();
  Trans.getTransferData("text/unicode", Str, StrLength);
  if (Str) {
    Str = Str.value.
      QueryInterface(Components.interfaces.nsISupportsString);
    return String(Str);
  } else {
    return "";
  }
}

// coordinate paster helper
function PasteCoords(Event) {
  if (
    ! Event.shiftKey && ! Event.altKey && Event.ctrlKey &&
    String.fromCharCode(Event.which).toLowerCase() == "v"
  ) {
    var PatCoords = /(\d+)[\s:,;](\d+)/i;
    var Coords = PatCoords.exec(unsafeWindow.GetClipboard());
    if (Coords) {
      if (new RegExp(Host + "/map\\.php", "I").test(window.location)) {
        document.getElementById("searchX").value = Coords[1];
        document.getElementById("searchY").value = Coords[2];
      } else if (
        new RegExp(Host + "/pohod\\.php", "i").test(window.location) &&
        document.getElementById("army_orders")
      ) {
        document.evaluate(
          "//input[@name='attackx']", document, null,
          XPathResult.FIRST_ORDERED_NODE_TYPE, null
        ).singleNodeValue.value = Coords[1];
        var Node = document.evaluate(
          "//input[@name='attacky']", document, null,
          XPathResult.FIRST_ORDERED_NODE_TYPE, null
        ).singleNodeValue;
        Node.focus();
        Node.value = Coords[2];
        Node.blur();
      }
    }
  }
}

// parse arbitrary page, use in callback
function GetDoc(URL, Callback) {
  var Rest = [];
  for (var N = 2; N < arguments.length; N++) {
    Rest.push(arguments[N]);
  }
  GM_xmlhttpRequest({
    method: "GET",
    url: URL,
    onload: function (responseDetails) {
      var Doc = document.implementation.createDocument("", "", null),
          HTML = document.createElement("html"),
          Head = document.createElement("head"),
          Body = document.createElement("body");
      Head.innerHTML = /<\s*head[^>]*>((?:.|\s)+?)<\s*\/head\s*>/mi.
        exec(responseDetails.responseText)[1];
      Body.innerHTML = /<\s*body[^>]*>((?:.|\s)+?)<\s*\/body\s*>/mi.
        exec(responseDetails.responseText)[1];
      Doc.appendChild(HTML);
      HTML.appendChild(Head);
      HTML.appendChild(Body);
      eval("Callback(Doc" + (Rest.length ? "," : "") + Rest.join(",") + ");");
    }
  });
}

// convert form name to unit ID ("uxx")
function FormToID(FormName) {
  var ID = /units\[(\d+)\]/i.exec(FormName);
  return (ID ? "u" + ID[1] : "");
}

/******************************************************************************/
// redir coords on success

var PageMatch = new RegExp(Host + "/pohod\\.php?.*sent=1", "i").
  test(window.location);
if (PageMatch && window.name) {
  var X = GM_getValue(window.name + "X");
  var Y = GM_getValue(window.name + "Y");
  var Form = document.forms.namedItem("form1");
  Form.elements.namedItem("attackx").value = X;
  Form.elements.namedItem("attacky").value = Y;
  Form.action = "pohod.php?attackx=" + X + "&attacky=" + Y;
  GM_deleteValue(window.name); GM_deleteValue(window.name + "Formation");
  GM_deleteValue(window.name + "FormationNum");
  GM_deleteValue(window.name + "X"); GM_deleteValue(window.name + "Y");
  window.name = "";
}

/******************************************************************************/
// march page 1
// display march cargo inline; link coords directly to reattack; display
// city name instead of coords; fix paging search params
// encumbrance/speed/etc on march page

function QuickGo(Event) {
  function SelectMarchType(Value) {
    var Found = false;
    for (let N = 0; N < MarchType.options.length; N++) {
      if (MarchType.options[N].value == Value) {
        Found = true;
        MarchType.selectedIndex = N;
      }
    }
    if (! Found) {
      alert(Translate("Could not find that march type."));
      return false;
    } else {
      return true;
    }
  }
  if (! window.name) {
    window.name = "QuickGo" + new Date().getTime();
  }
  var SubmitButton = document.evaluate(
    "//input[@type='submit']",
    document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
  ).singleNodeValue;
  document.forms.namedItem("form1").
    removeEventListener("submit", RetainManualFormation, true);
  var MarchType = document.evaluate(
    "//form[@name='form1']/descendant::*[@id='type']",
    document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
  ).singleNodeValue
  var PatNum = /^(.+)(\d+)$/i;
  var ID = PatNum.exec(Event.target.getAttribute("id").slice(3));
  GM_setValue(window.name, Number(ID[2]));
  var Formation = document.getElementById("GM_Formation");
  var FormationNum = Formation.options[Formation.selectedIndex].value;
  Formation = Formation.options[Formation.selectedIndex].innerHTML;
  var Form = document.forms.namedItem("form1").elements;
  GM_setValue(window.name + "Formation", Formation);
  GM_setValue(window.name + "FormationNum", FormationNum);
  GM_setValue(
    window.name + "X", Form.namedItem("attackx").value
  );
  GM_setValue(
    window.name + "Y", Form.namedItem("attacky").value
  );
  var Success;
  switch (ID[1]) {
    case "QAtt":
    case "Attack": Success = SelectMarchType("3"); break;
    case "QScav":
    case "Scavenge": Success = SelectMarchType("5"); break;
    case "QTransfer":
    case "Transfer": Success = SelectMarchType("8"); break;
    case "Spy":
      var QWNode = document.forms.namedItem("form1").elements.
        namedItem(UKMode ? "u9": "units[9]");
      var Temp = Number(QWNode.value);
      SetAll(false); QWNode.value = String(Math.max(Temp, 1));
      Success = SelectMarchType("7");
      break;
    case "QSupport":
    case "Support": Success = SelectMarchType("2"); break;
  }
  if (Success) SubmitButton.click();
}

// update march info on change
function UpdateMarchNums(Event) {
  Target = [
    document.evaluate(
      "//input[@name='attackx']", document, null,
      XPathResult.FIRST_ORDERED_NODE_TYPE, null
    ).singleNodeValue.value,
    document.evaluate(
      "//input[@name='attacky']", document, null,
      XPathResult.FIRST_ORDERED_NODE_TYPE, null
    ).singleNodeValue.value
  ];
  var MapData = GM_getValue(
    SavedHost + "_MapData_" + String(Target[0]) + "_" + String(Target[1]),
    ""
  );
  var CastleName = "";
  MapData ? CastleName = eval(MapData)[4] || "" : CastleName = "";
  document.getElementById("GM_CastleLabel").innerHTML =
    " <a href='" + Host + "/map.php?setx=" +
        Target[0] + "&sety=" + Target[1] + "'>&rarr;</a> " + CastleName;

  var Pop = NonMysticPop = Encumbrance = Speed = Damage = Mystic =
    DamageBonus = Temp = 0;
  var Form = document.forms.namedItem("form1");
  Form.action = "pohod.php?attackx=" + Target[0] + "&attacky=" + Target[1];
  for (var N = 0; N < Form.elements.length; N++) {
    DamageBonus = 0;
    if (UKMode) {
      var UnitID = Form[N].name;
    } else {
      var UnitID = FormToID(Form[N].name);
    }
    if (UnitID in Units) {
      var Marched = Number(Form[N].value);
      if (UnitID in ArcherList) {
        DamageBonus += ArcherLevels[ArcherLevel];
      }
      if (UnitID in MysticList) {
        DamageBonus += MysticLevels[MysticLevel];
      }
      if (UnitID in UnitUpgrades) {
        DamageBonus +=
          UnitUpgrades[UnitID][0] * UnitUpgrades[UnitID][1];
      }
      Temp = Math.floor(
        Marched * (1 + DamageBonus / 100) *
        Units[UnitID]["building"] * Units[UnitID]["wallcrasher"]
      );
      Damage += Temp;
      if (UnitID in MysticList) Mystic += Temp;
      Encumbrance += Marched * Units[UnitID]["carry"];
      Pop += Marched * Units[UnitID]["pop"];
      NonMysticPop += Marched * Units[UnitID]["pop"] * ! (UnitID in MysticList);
      if (Marched > 0) {
        Speed = Math.max(
          Speed,
          Math.round(
            Units[UnitID]["speed"] / (1 + ScoutLevels[ScoutLevel] / 100)
          )
        );
      }
    }
  }
  var Node = document.getElementById("pop_span");
  Node.innerHTML = String(Pop);
  Node = document.getElementById("GM_Non-Mystics");
  Node.innerHTML = String(NonMysticPop);
  Node = document.getElementById("GM_Encumbrance");
  Node.innerHTML = String(Encumbrance);
  Node = document.getElementById("GM_Speed");
  if (Speed) {
    Node.innerHTML = MarchTime(Dist(Target), Speed, "&nbsp;", false, false, true);
  } else {
    Node.innerHTML = MarchTime(0, Speed, "&nbsp;", false, false, false);
  }
  Node = document.getElementById("GM_Damage");
  Node.innerHTML = String(Damage);
  Node = document.getElementById("GM_Mystics");
  Node.innerHTML = String(Mystic);
  Node = document.getElementById("GM_Castles");
  Node.innerHTML = CalcCastleDists(Target)[0];
}

function HandleTransfer(Event) {
  var Type = Event.target.id.slice(3);
  var SubmitButton = document.evaluate(
    "//input[@type='submit']",
    document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
  ).singleNodeValue;
  switch (Type) {
    case ("CastleSelect"):
      var CanTransfer = document.evaluate(
        "//form[@name='form1']/descendant::*[@id='type']/option[@value='8']",
        document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
      ).singleNodeValue;
      if (CanTransfer) CanTransfer.selected = true;
      var Match = /(\d+):(\d+)/i.exec(
        Event.target.options[Event.target.selectedIndex].value
      );
      var Node = document.evaluate(
        "//input[@name='attackx']", document, null,
        XPathResult.FIRST_ORDERED_NODE_TYPE, null
      ).singleNodeValue;
      Node.focus(); Node.value = Match[1]; Node.blur();
      Node = document.evaluate(
        "//input[@name='attacky']", document, null,
        XPathResult.FIRST_ORDERED_NODE_TYPE, null
      ).singleNodeValue;
      Node.focus(); Node.value = Match[2]; Node.blur();
      break;
  }
  document.getElementById("GM_CastleSelect").selectedIndex = 0;
}

function CreateCancelTimer(Event) {
  var CancelMarchIntervals = {};

  // update cancel timers for activated marches
  function UpdateCancelTime(ID, OneWay, Arrives) {
    Arrives = Arrives.getHours() * 3600 + Arrives.getMinutes() * 60 +
      Arrives.getSeconds();
    var Now = document.getElementById("theClock").innerHTML;
    if (Now) {
      Now = Now.split(":");
      Now = Number(Now[0]) * 3600 + Number(Now[1]) * 60 + Number(Now[2]);
      if (Arrives < Now) Arrives += 86400;
    } else {
      Now = NaN;
    }
    var DeltaTime = (Arrives - Now) % 86400;
    var CancelTime = Number((DeltaTime > 0) * (OneWay - DeltaTime));
    var TimeLeft = Number((DeltaTime > 0) * DeltaTime);
    if (DeltaTime > 0 && DeltaTime < 36000) {
      document.getElementById(ID).innerHTML = HMS(CancelTime) + "<br />" +
        HMS(TimeLeft);
    } else {
      document.getElementById(ID).innerHTML = "--<br />--";
      window.clearInterval(CancelMarchIntervals[ID]);
      delete CancelMarchIntervals[ID];
    }
  }

  var Node = document.evaluate(
    "ancestor::td[1]/preceding-sibling::td[1]/descendant::a",
    Event.target, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
  ).singleNodeValue;
  var ID = /posted_id=(\d+)/i.exec(Node.href)[1];

  function UpdateCancelTimeCallback(Doc) {
    var NewNode = document.getElementById("GM_March" + ID);
    if (! NewNode) {
      var Nodes = Doc.evaluate(
            "//ns:div[@class='textformbox'][1]/ns:div[@class='row']" +
              "[position()=4 or position()=5]/ns:div[2]",
            Doc, nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
          ),
          Arrives = LocalTime(Nodes.snapshotItem(0).innerHTML),
          Returns = LocalTime(Nodes.snapshotItem(1).innerHTML);
      NewNode = document.createElement("div");
      NewNode.id = "GM_March" + ID; NewNode.innerHTML = "";
      NewNode.setAttribute("style", "font-size: 80%;");
      Event.target.parentNode.appendChild(NewNode);
      var OneWay = (Returns.getTime() - Arrives.getTime()) / 1000;
      UpdateCancelTime("GM_March" + ID, OneWay, Arrives);
      var IntervalID = window.setInterval(
        UpdateCancelTime, 1000, "GM_March" + ID, OneWay, Arrives
      );
      CancelMarchIntervals["GM_March" + ID] = IntervalID;
    }
  }
  GetDoc(Node.href, UpdateCancelTimeCallback);
}

var PageMatch = new RegExp(Host + "/pohod\\.php", "i").test(window.location);
var MarchesFilter = document.getElementsByClassName("marchesFilter").length;
if (PageMatch && MarchesFilter) {
  // clear any previous instructions
  if (window.name) {
    GM_deleteValue(window.name); GM_deleteValue(window.name + "Formation");
    GM_deleteValue(window.name + "FormationNum");
    GM_deleteValue(window.name + "X"); GM_deleteValue(window.name + "Y");
    window.name = "";
  }
  window.addEventListener("keypress", PasteCoords, true);
  var TimerColumn = document.evaluate(
    "//*[@class='box'][1]/table[1]/thead[1]/tr[1]/td[2]",
    document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
  ).singleNodeValue;
  if (TimerColumn) TimerColumn.setAttribute("style", "width: 7em");
  var Timers = document.evaluate(
    "//div[starts-with(@id, 'bxx')]",
    document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  for (var N = 0; N < Timers.snapshotLength; N++) {
    Timers.snapshotItem(N).setAttribute("style", "text-decoration: underline;");
    Timers.snapshotItem(N).parentNode.
      addEventListener("click", CreateCancelTimer, true);
  }
  var Match, StrClean, Cargo, CargoText;
  var PatCargo = new RegExp(
    "(?:(\\d+?)\\s*" + Translate("Gold") + "[\\s\\S]*?(\\d+?)\\s*" + Translate("Iron") +
      "[\\s\\S]*?(\\d+?)\\s*" + Translate("Wood") + "[\\s\\S]*?" +
      "(\\d+?)\\s*" + Translate("Food") + "[\\s\\S]*?)?(\\d+?)\\s*" + Translate("People"),
    "i"
  );
  var PatCoords = /(\d+):(\d+)/i;
  var SelNodes = document.evaluate(
    "//a[contains(@href, 'javascript:alert')]",
    document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  var Search = CleanSearch(
    CleanSearch(window.location.search, "attackx", ""), "attacky", ""
  );
  Search = CleanSearch(CleanSearch(Search, "motivated", ""), "sent", "");
  var Form = document.forms.namedItem("form1").elements;
  var Target = Form.namedItem("attackx").value + ":" +
    Form.namedItem("attacky").value;
  for (var N = 0; N < SelNodes.snapshotLength; N++) {
    StrClean = SelNodes.snapshotItem(N).getAttribute("href");
    Match = PatCargo.exec(StrClean);
    if (Match) {
      Cargo = PatCargo.exec(Match[0]);
      CargoText = "";
      for (var M = (Cargo[1] != null ? 1 : Cargo.length - 1); M < Cargo.length; M++) {
        var Num = Number(Cargo[M]);
        if (Num < 1000) {
          CargoText += String(Num) + "/";
        } else {
          CargoText += String(Math.floor(Num / 1000)) + "k/";
        }
      }
      CargoText =
        "<small>" + CargoText.slice(0, CargoText.length - 1) + "</small>";
      var LinkNode = SelNodes.snapshotItem(N).
        parentNode.previousSibling.previousSibling;
      var Match = PatCoords.exec(LinkNode.innerHTML);
      if (Search == "?") Search = "";
      LinkNode.innerHTML =
        "<a href='" + Host + "/pohod.php?attackx=" +
        Match[1] + "&attacky=" + Match[2] +
        (Search ? "&" + Search.slice(1) : "") + "'>&clubs;</a>" +
        " <a href='" + Host + "/map.php?setx=" +
        Match[1] + "&sety=" + Match[2] + "'>&rarr;</a>" +
        LinkNode.innerHTML +
        (
          new RegExp(Target, "i").test(LinkNode.innerHTML)
            ? "*"
            : ""
        );
      SelNodes.snapshotItem(N).innerHTML = CargoText;
    }
  }

  // collect castle coordinate information
  var Castles = document.evaluate(
    "//select[@name='filter']/option", document, null,
    XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  if (Castles.snapshotLength) {
    var PatCastle = /(.+)\s+?\((\d+):(\d+)\)/i;
    var CastleArray = [];
    for (var N = 0; N < Castles.snapshotLength; N++) {
      var Match = PatCastle.exec(Castles.snapshotItem(N).innerHTML);
      if (Match) {
        Match.push(Castles.snapshotItem(N).value);
        CastleArray.push(Match);
      }
    }
    GM_setValue(SavePrefix + "Castles", uneval(CastleArray));
    PlayerCastles = eval(CastleArray.toSource());
  }

  var TargetX = document.evaluate(
    "//input[@name='attackx']", document, null,
    XPathResult.FIRST_ORDERED_NODE_TYPE, null
  ).singleNodeValue.value;
  var TargetY = document.evaluate(
    "//input[@name='attacky']", document, null,
    XPathResult.FIRST_ORDERED_NODE_TYPE, null
  ).singleNodeValue.value;

  // fix paging links
  var Paging = document.evaluate(
    "//p[@class='paging']", document, null,
    XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  var SearchOrig = window.location.search;
  var Search;
  if (Paging.snapshotLength) {
    var Links = document.evaluate(
      "descendant::a", Paging.snapshotItem(0), null,
      XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
    );
    Search = CleanSearch(
      CleanSearch(
        CleanSearch(SearchOrig, "page", ""), "attackx", ""
      ),
      "attacky",
      "attackx=" + TargetX + "&attacky=" + TargetY
    );
    for (var N = 0; N < Links.snapshotLength; N++) {
      Links.snapshotItem(N).href += (Search ? "&" + Search.slice(1) : "");
    }
    Paging.snapshotItem(1).innerHTML = Paging.snapshotItem(0).innerHTML;
  }

  // fix sorting links
  var Sorting = document.evaluate(
    "//table/thead/tr/td/a[contains(@href, 'sort=')]", document, null,
    XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  Search = CleanSearch(
    CleanSearch(
      CleanSearch(
        CleanSearch(CleanSearch(SearchOrig, "sort", ""), "sort_type", ""),
        "filter",
        ""
      ),
      "attackx",
      ""
    ),
    "attacky",
    "attackx=" + TargetX + "&attacky=" + TargetY
  );
  for (var N = 0; N < Sorting.snapshotLength; N++) {
    Sorting.snapshotItem(N).href += (Search ? "&" + Search.slice(1) : "");
  }

  // fix filter function to retain headers
  var Node = document.evaluate(
    "//select[@name='filter']/option", document, null,
    XPathResult.FIRST_ORDERED_NODE_TYPE, null
  ).singleNodeValue;
  Node.parentNode.addEventListener(
    "change",
    function (Event) {
      var SearchOrig = window.location.search;
      SearchOrig = CleanSearch(
        CleanSearch(
          CleanSearch(CleanSearch(SearchOrig, "filter", ""), "page", ""),
          "attackx",
          ""
        ),
        "attacky",
        "attackx=" + TargetX + "&attacky=" + TargetY
      );
      SearchOrig =
        CleanSearch(CleanSearch(SearchOrig, "sent", ""), "motivated", "");
      window.location.href = window.location.pathname +
        SearchOrig + (SearchOrig != "" ? "&" : "?") +
        "filter=" + Event.target.options[Event.target.selectedIndex].value;
    },
    true
  );

  // display town name instead of coords
  var Nodes = document.evaluate(
    "//img[contains(@src,'march_back.gif') or contains(@src,'march_forward.gif')]/ancestor::tr[1]",
    document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  var PatCoords = /(\d+):(\d+)/i;
  for (var N = 0; N < Nodes.snapshotLength; N++) {
    var Node = Nodes.snapshotItem(N).childNodes[5];
    var Coords = PatCoords.exec(Node.innerHTML).slice(1);
    var TempName =
      eval(GM_getValue(SavedHost + "_MapData_" + Coords[0] + "_" + Coords[1], ""))
        ||
      "";
    if (TempName) TempName = TempName[4];
    if (TempName) {
      Node.innerHTML = Node.innerHTML.replace(
        new RegExp("\\s*" + Coords[0] + ":" + Coords[1] + "\\s*", "i"),
        "<span style='font-size: 80%' title='" + Coords[0] +
          ":" + Coords[1] + "'>" + TempName + "</span>"
      );
    }
    Node = Nodes.snapshotItem(N).childNodes[7];
    Coords = PatCoords.exec(Node.innerHTML).slice(1);
    TempName =
      eval(GM_getValue(SavedHost + "_MapData_" + Coords[0] + "_" + Coords[1], ""))
        ||
      "";
    if (TempName) TempName = TempName[4];
    if (TempName) {
      Node.innerHTML = Node.innerHTML.replace(
        new RegExp("\\s*" + Coords[0] + ":" + Coords[1] + "\\s*", "i"),
        "<span style='font-size: 80%' title='" + Coords[0] + ":" + Coords[1] +
          "'>" + TempName + "</span>"
      );
    }
  }
  var Form = document.forms.namedItem("form1");

  // find number of troops available in particular form
  function FindNum(Form) {
    return /<strong>(\d+)<\/strong>/i.exec(
      Form.parentNode.previousSibling.previousSibling.innerHTML
    )[1];
  }

  // update single unit
  function SetupMax(Event) {
    var Match = /GM_Unit_(.*?)_(\d+)/i.exec(Event.target.getAttribute("class"));
    var ID = Match[1]; var Value = Match[2];
    SetMaximum(ID, Value);
  }
  function SetMaximum(ID, Value) {
    var Form = document.forms.namedItem("form1");
    var Item = Form.elements.
      namedItem(UKMode ? ID : "units[" + ID.slice(1) + "]");
    Item.focus();
    Item.value = Value;
    Item.blur();
  }

  // update all units on/off
  function SetupSetAll(Event) {
    if (Event.target.id == "GM_All") {
      SetAll(true);
    } else {
      SetAll(false);
    }
  }
  function SetAll(On) {
    var Form = document.forms.namedItem("form1"), UnitID;
    for (var N = 0; N < Form.elements.length; N++) {
      UKMode ? UnitID = Form[N].name : UnitID = FormToID(Form[N].name);
      if (/^u\d+$/i.test(UnitID)) {
        if (On) {
          SetMaximum(UnitID, FindNum(Form[N]));
        } else {
          SetMaximum(UnitID, "0");
        }
      }
    }
  }

  // single infantry
  function SingleInfantry() {
    var Form = document.forms.namedItem("form1"), UnitID;
    for (var N = 0; N < Form.elements.length; N++) {
      UKMode ? UnitID = Form[N].name : UnitID = FormToID(Form[N].name);
      if (/^u(1|2|3|4)$/i.test(UnitID)) {
        var Available = Number(FindNum(Form[N]));
        SetMaximum(UnitID, Available ? "1" : "0");
      }
    }
  }

  // +/- 1
  function ChangeOne(Event) {
    var Delta;
    Event.target.innerHTML.slice(0, 1) == "+" ? Delta = 1 : Delta = -1;
    var ActualTarget = Event.target.parentNode.parentNode.previousSibling;
    ActualTarget.focus();
    ActualTarget.value = String(Number(ActualTarget.value) + Delta);
    ActualTarget.blur();
  }

  // paste copied army
  function PasteArmyMarch(Event) {
    var ArmyArray = eval(GM_getValue(SavePrefix + "_CopyArmy", ""));
    if (ArmyArray) {
      var Form = document.forms.namedItem("form1").elements;
      var NotEnough = false, UnitID, ArmyCount;
      for (var N = 1; N < ArmyArray.length; N++) {
        UKMode ? UnitID = "u" + String(N) : UnitID = "units[" + String(N) + "]";
        ArmyCount = ArmyArray[N] || 0;
        var TextBox = Form.namedItem(UnitID);
        if (TextBox) {
          var Available = Number(FindNum(TextBox));
          if (Available < ArmyCount) NotEnough = true;
          TextBox.value = String(Math.min(ArmyCount, Available));
        } else if (ArmyCount) {
          NotEnough = true;
        }
      }
      if (NotEnough)
        alert(Translate("Warning: not enough troops to paste all."));
    }
  }

  // invert army selection
  function PasteArmyInvert(Event) {
    var UnitID;
    for (var N = 0; N < Form.elements.length; N++) {
      UKMode ? UnitID = Form[N].name : UnitID = FormToID(Form[N].name);
      if (UnitID in Units) {
        var Available = Number(FindNum(Form[N]));
        var Used = Number(Form[N].value);
        if (Used > 0) Form[N].value = String(Available - Used);
      }
    }
  }

  // move some forms around
  var InsertionPoint, Node;
  var AllNone = document.evaluate(
    "//div[@class='formbox']/div[position()=1]",
    document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
  ).singleNodeValue;
  AllNone.innerHTML = "<div class='right'>" +
    "<a href='javascript:void(0)' id='GM_All'>" + Translate("All") + "</a> / " +
    "<a href='javascript:void(0)' id='GM_None'>" + Translate("None") + "</a> / " +
    "<a href='javascript:void(0)' id='GM_Infantry'>" + Translate("Single Infantry") + "</a> / " +
    "<a href='javascript:void(0)' id='GM_Paste'>" + Translate("Paste") + "</a> / " +
    "<a href='javascript:void(0)' id='GM_Invert'>" + Translate("Invert") + "</a>";
  document.getElementById("GM_All").
    addEventListener("click", SetupSetAll, true)
  document.getElementById("GM_None").
    addEventListener("click", SetupSetAll, true)
  document.getElementById("GM_Infantry").
    addEventListener("click", SingleInfantry, true)
  document.getElementById("GM_Paste").
    addEventListener("click", PasteArmyMarch, true)
  document.getElementById("GM_Invert").
    addEventListener("click", PasteArmyInvert, true)

  var MarchType = document.evaluate(
    "//form[@name='form1']/descendant::*[@id='type']",
    document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
  ).singleNodeValue.parentNode.parentNode;
  var Submit = MarchType.nextSibling.nextSibling;
  
  function RetainManualFormation(Event) {
    window.name = "QuickGo" + new Date().getTime();
    var Formation = document.getElementById("GM_Formation");
    var FormationNum = Formation.options[Formation.selectedIndex].value;
    Formation = Formation.options[Formation.selectedIndex].innerHTML;
    GM_setValue(window.name, -1);
    GM_setValue(window.name + "Formation", Formation);
    GM_setValue(window.name + "FormationNum", FormationNum);
    GM_setValue(window.name + "X", TargetX);
    GM_setValue(window.name + "Y", TargetY);
    Form.removeEventListener("submit", RetainManualFormation, true);
  }
  Form.addEventListener("submit", RetainManualFormation, true);

  [
    ["Attack", "3"], ["Spy", "7"], ["Transfer", "8"], ["Support", "2"],
    ["Scavenge", 5]
  ].forEach(
    function (Option) {
      var NewNode = document.createElement("input");
      var Exists = document.evaluate(
        "//form[@name='form1']/descendant::*[@id='type']/option[@value='" +
          Option[1] + "']",
        document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null 
      ).singleNodeValue;
      if (Exists) {
        NewNode.value = Exists.innerHTML
        NewNode.type = "button";
        NewNode.id = "GM_" + Option[0] + "0";
        NewNode.addEventListener("click", QuickGo, true);
        Submit.appendChild(document.createElement("br"));
        Submit.appendChild(NewNode);
      }
    }
  );
  
  var Table = document.getElementById("units_to_send");
  Table.parentNode.setAttribute("style", "margin: 0; padding: 0;");
  var NewTable = document.createElement("table");
  NewTable.setAttribute("width", "100%");
  NewTable.innerHTML = "<tr><td valign='top' id='GM_Tab1'></td>" +
    "<td valign='top' align='right' width='225px'>" +
    "<span id='GM_Tab2'></span></td></tr>";
  Table.parentNode.insertBefore(NewTable, Table);

  var Formations = GM_getValue(SavePrefix + "Formations", "");
  Formations ? Formations = eval(Formations) : Formations = [[Translate("Default"), "0"]];
  var FormationNode = document.createElement("div");
  var TempText = "";
  Formations.forEach(
    function (Option) {
      TempText += "<option value='" + Option[1] + "'>" +
        Option[0] + "</option>";
    }
  );
  FormationNode.innerHTML = Translate("Formation") +
    ":<br /><select id='GM_Formation'>" + TempText + "</select>";

  InsertionPoint = document.getElementById("GM_Tab1");
  InsertionPoint.appendChild(AllNone);
  InsertionPoint.appendChild(Table);
  InsertionPoint = document.getElementById("GM_Tab2");
  InsertionPoint.appendChild(MarchType);
  MarchType.parentNode.insertBefore(FormationNode, MarchType.nextSibling);

  var FormationNode = document.getElementById("GM_Formation");
  FormationNode.style.color = "red";
  FormationNode.style.textDecoration = "blink";
  var SelNodes = FormationNode.options;
  var Found = false;
  for (var N = 0; N < SelNodes.length; N++) {
    if (SelNodes[N].innerHTML == DefaultAttack) {
      SelNodes[N].selected = true; Found = true; break;
    }
  }
  if (! Found && DefaultAttack)
    alert(Translate("Default attack formation not found."));

  InsertionPoint.appendChild(Submit);
  var PopNode = document.getElementById("pop_span");
  PopNode.innerHTML = String(Pop);
  PopNode = PopNode.parentNode.parentNode;
  InsertionPoint.appendChild(PopNode);
  if (! UKMode) {
    CargoNode = document.getElementById("sum_cargo");
    CargoNode.parentNode.parentNode.parentNode.removeChild(CargoNode.parentNode.parentNode);
  }

  [
    "Speed", "Damage", "Mystics", "Encumbrance", "Non-Mystics", "Castles"
  ].forEach(
    function (Option) {
      var NewNode = document.createElement("div");
      NewNode.setAttribute("class", "row");
      NewNode.innerHTML = "<div class='left'><label" +
        (Option == "Speed" ? " id='GM_SpeedLabel'" : "") +
        ">" + Translate(Option) + ":</label></div>" +
        (Option == "Speed"
          ? "<table style='text-align: right'>" +
            "<tr><td style='margin: 0; padding: 0;'>"
          : "") +
        "<div " + (Option == "Speed" ? "style='font-size: 75%;'" : "") +
        "class=\"right\" id=\"GM_" + Option + "\"></div>" +
        (Option == "Speed"
          ? "</td><td style='margin: 0; padding: 0; font-size: 75%;' " +
            "id='GM_QuickControls'></td></tr></table>"
          : "");
      if (Option != "Castles") {
        PopNode.parentNode.insertBefore(NewNode, PopNode.parentNode.lastChild);
      } else {
        NewNode.innerHTML = "<div style='margin: 5px; padding: 5px'>" +
          NewNode.innerHTML + "</div>";
        NewTable.parentNode.appendChild(NewNode);
      }
    }
  );
  var QuickNode = document.createElement("div");
  QuickNode.setAttribute("class", "row");
  QuickNode.innerHTML = "<label>" + Translate("Quick Roads") +
    ": </label><input id='" +
    "GM_QuickRoads' type='checkbox' />";
  InsertionPoint = document.getElementById("GM_SpeedLabel");
  InsertionPoint.parentNode.
    insertBefore(QuickNode, InsertionPoint);
  document.getElementById("GM_QuickRoads").
    addEventListener("change", UpdateMarchNums, true);

  Node = document.evaluate(
    "//input[@name='attackx']", document, null,
    XPathResult.FIRST_ORDERED_NODE_TYPE, null
  ).singleNodeValue;
  Node.type = "text"; Node.size = 4;
  Node.addEventListener("blur", UpdateMarchNums, true);
  Node.addEventListener(
    "change",
    function () {document.getElementById("GM_QuickRoads").checked = false;},
    "true"
  );
  Form.action = "pohod.php?attackx=" + Node.value;
  Node = document.evaluate(
    "//input[@name='attacky']", document, null,
    XPathResult.FIRST_ORDERED_NODE_TYPE, null
  ).singleNodeValue;
  Node.type = "text"; Node.size = 4;
  Node.addEventListener("blur", UpdateMarchNums, true);
  Node.addEventListener(
    "change",
    function () {document.getElementById("GM_QuickRoads").checked = false;},
    "true"
  );
  Form.action += "&attacky=" + Node.value;
  var CastleSelection = document.createElement("select");
  CastleSelection.innerHTML += "<option value=''></option>";
  for (var N = 0; N < PlayerCastles.length; N++) {
    CastleSelection.innerHTML += "<option value='" + PlayerCastles[N][2] + ":" +
      PlayerCastles[N][3] + "'>" + PlayerCastles[N][0] + "</option>";
  }
  CastleSelection.id = "GM_CastleSelect";
  CastleSelection.addEventListener("change", HandleTransfer, true);
  Node.parentNode.insertBefore(CastleSelection, Node.nextSibling);
  var CastleLabel = document.createElement("span");
  CastleLabel.id = "GM_CastleLabel";
  CastleLabel.style.color = "white";
  Node.parentNode.insertBefore(CastleLabel, CastleSelection.nextSibling);
  UpdateMarchNums();
  window.setInterval(UpdateMarchNums, 1000);

  var SpeedBRs = document.evaluate(
    "descendant::br", document.getElementById("GM_Speed"), null,
    XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  var PatFirst = /\(\d+-\d+\)$/i;
  for (var N = 0; N < SpeedBRs.snapshotLength; N++) {
    if (PatFirst.test(SpeedBRs.snapshotItem(N).previousSibling.textContent)) {
      [
        ["QAtt", "A"], ["QScav", "S"], ["QTransfer", "T"]
      ].forEach(
        function (Option) {
          var Transfers = document.evaluate(
            "//form[@name='form1']/descendant::*[@id='type']/option[@value='8']",
            document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null 
          ).singleNodeValue;
          if (Option[1] != "T" || Transfers) {
            var Key = document.createElement("span");
            Key.innerHTML = "&nbsp;<a href='javascript:void(0);' id='GM_" +
              Option[0] + String(N) + ">" + Translate(Option[1]) + "</a>";
            Key.addEventListener("click", QuickGo, true);
            document.getElementById("GM_QuickControls").appendChild(Key);
          }
        }
      );
    }
    document.getElementById("GM_QuickControls").
      appendChild(document.createElement("br"));
  }
  var UnitID;
  for (var N = 0; N < Form.elements.length; N++) {
    UKMode ? UnitID = Form[N].name : UnitID = FormToID(Form[N].name);
    if (UnitID in Units) {
      // tweak handler to update encumbrance and speed as well as pop
      Form[N].addEventListener("blur", UpdateMarchNums, true);
      // additional % links
      var Num = FindNum(Form[N]);
      var NewLinks = document.createElement("span");
      NewLinks.innerHTML =
        " <span style='font-size: 75%'>" +
        "<a href='javascript:void(0);' class='GM_Unit_ChangeOne'>-1</a>" +
        " <a href='javascript:void(0);' class='GM_Unit_ChangeOne'>+1</a>" +
        " <a class='GM_Unit_" + UnitID + "_0' href='javascript:void(0);'" +
        ">0%</a>" +
        " <a class='GM_Unit_" + UnitID + "_" + String(Math.floor(Num / 3)) +
        "' href='javascript:void(0);'>33%</a>" +
        " <a class='GM_Unit_" + UnitID + "_" + String(Math.floor(Num / 2)) +
        "' href='javascript:void(0);'>50%</a>" +
        " <a class='GM_Unit_" + UnitID + "_" + String(Num) +
        "' href='javascript:void(0);'>100%</a>";
      var CurrSib = Form[N].nextSibling;
      // remove existing text nodes--too hard to find insertion point
      while (CurrSib != null) {
        Form[N].parentNode.removeChild(CurrSib);
        CurrSib = Form[N].nextSibling;
      }
      Form[N].parentNode.insertBefore(NewLinks, Form[N].nextSibling);
    }
  }

  var Nodes = document.evaluate(
    "//a[starts-with(@class, 'GM_Unit_')]",
    document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  for (var N = 0; N < Nodes.snapshotLength; N++) {
    var Node = Nodes.snapshotItem(N);
    if (Node.getAttribute("class") == "GM_Unit_ChangeOne") {
      Node.addEventListener("click", ChangeOne, true);
    } else {
      Node.addEventListener("click", SetupMax, true);
    }
  }
}

/******************************************************************************/
// march page 2--default attack formation, moving oft-used controls to top

function InitMarchUpdate2(Event) {
  function CalcMarch2Time(Event) {
    OneWay = document.getElementById(UKMode ? "z" : "timeDirection").innerHTML;
    if (OneWay == "-") {OneWay = 0;} else {OneWay = ToSeconds(OneWay);}

    var NameNode = document.getElementById("GM_CastleName");
    var NewNode = document.getElementById("GM_Timing");
    var ServerArriveNode = document.getElementById("GM_ServerArrive");
    var ServerTime = document.getElementById("theClock").innerHTML;
    if (ServerTime == "") ServerTime = "00:00:00";
    var TargetDistance = Dist(
      [
        document.getElementById(UKMode ? "att_x" : "targetX").value,
        document.getElementById(UKMode ? "att_y" : "targetY").value
      ],
      [Home[0], Home[1]],
      true
    );  
    var BaseSpeed = OrigOneWay / 60 / TargetDistance / SpeedMod;
    var EffSpeed = StripDecimals(Round(OneWay / 60 / TargetDistance, 0.01), 2);
    var SpeedNode = document.getElementById("pohodCargo").
      parentNode.previousSibling.previousSibling.getElementsByTagName("div")[1];
    SpeedNode.innerHTML = SpeedNode.innerHTML.split(" ")[0] + " (" +
      Translate("Effective Speed") + " " + EffSpeed + ")";
    var LocalArrive = new Date();
    LocalArrive.setSeconds(LocalArrive.getSeconds() + OneWay);
    var LocalReturn = new Date();
    LocalReturn.setSeconds(LocalReturn.getSeconds() + 2 * OneWay);
    ServerArriveNode.innerHTML = Translate("Server Arrival") + ": " +
      HMS((ToSeconds(ServerTime) + OneWay) % 86400) + "<br />" +
      Translate("Server Return") + ": " +
      HMS((ToSeconds(ServerTime) + 2 * OneWay) % 86400) + "<br />" +
      Translate("Local Arrival") + ": " +
      DateFormat(LocalArrive) + "<br />" + Translate("Local Return") + ": " +
      DateFormat(LocalReturn);
    var Speed = SpeedNode.innerHTML.split(" ")[0];
    var Now = new Date();
    NewNode.innerHTML = "<div class='left'>" + Translate("Timing (no coins)") +
      ":</div><div class='right'>" +
      MarchTime(TargetDistance, BaseSpeed, null, null, true, false, Speed) + "</div>";
    GetCastleName(
      [
        0, document.getElementById(UKMode ? "att_x" : "targetX").value,
        document.getElementById(UKMode ? "att_y" : "targetY").value
      ],
      "GM_CastleName"
    );
  }

  var OrigOneWay, SpeedFrac, SpeedMotivation, SpeedMod;
  var TempNode = document.getElementById("motiv" + (! UKMode ? "_speed" : ""));
  if (TempNode) TempNode.addEventListener("change", CalcMarch2Time, true);
  document.getElementById(UKMode ? "ppp" : "speed").
    addEventListener("change", CalcMarch2Time, true);

  if (Event) {
    OrigOneWay =
      document.getElementById(UKMode ? "z" : "timeDirection").innerHTML;
    if (OrigOneWay == "-") {
      OrigOneWay = 0;
    } else {
      OrigOneWay = ToSeconds(OrigOneWay);
    }
    SpeedFrac =
      document.getElementById(UKMode ? "ppp" : "speed").selectedIndex / 10;
    document.getElementById(UKMode ? "ppp" : "speed").
      addEventListener("change", CalcMarch2Time, true);
    SpeedMotivation = document.getElementById("motiv" + (! UKMode ? "_speed" : ""));
    if (SpeedMotivation)
      SpeedMotivation.addEventListener("change", CalcMarch2Time, true);
    SpeedMotivation
      ? SpeedMotivation =
          1 - Number(SpeedMotivation.options[SpeedMotivation.selectedIndex].value) /
            100
      : SpeedMotivation = 1;
  } else {
    OneWay = 0; SpeedFrac = 0; SpeedMotivation = 1; 
  }
  SpeedMod = (1 + SpeedFrac) * SpeedMotivation;
  if (March2IntervalID) window.clearInterval(March2IntervalID);
  CalcMarch2Time();
  March2IntervalID = window.setInterval(CalcMarch2Time, 1000);
}

var PageMatch = new RegExp(Host + "/pohod\\.php", "i").test(window.location);
var MarchesFilter = document.getElementsByClassName("marchesFilter").length;
if (PageMatch && ! MarchesFilter) {
  var Options = document.getElementById("army_orders").options;
  var Formations = [];
  for (var N = 0; N < Options.length; N++) {
    Formations.push([Options[N].innerHTML, Options[N].value]);
  }
  GM_setValue(SavePrefix + "Formations", uneval(Formations));

  var DefaultSpeed = -1;
  var DefaultFormation = "", DefaultFormationNum = "0";
  if (window.name) {
    DefaultSpeed = GM_getValue(window.name, -1);
    DefaultFormation = GM_getValue(window.name + "Formation");
    DefaultFormationNum = GM_getValue(window.name + "FormationNum");
  }
  var SelNodes = document.evaluate(
    "//select[@id='army_orders']/option",
    document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  var Found = false;
  for (var N = 0; N < SelNodes.snapshotLength; N++) {
    if (SelNodes.snapshotItem(N).innerHTML == DefaultFormation) {
      SelNodes.snapshotItem(N).selected = true; Found = true; break;
    }
  }
  var Options = document.getElementById(UKMode ? "ppp" : "speed").options;
  var ButtonNode = document.evaluate(
    "//div[@class='buttonrow']",
    document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  ButtonNode = ButtonNode.snapshotItem(1);
  Formation.style.color = "red";
  Formation.style.textDecoration = "blink";
  var Node = document.getElementById(UKMode ? "z" : "timeDirection"),
      NewNode, NameNode, ServerArriveNode;

  var March2IntervalID;
  Node.addEventListener("DOMNodeInserted", InitMarchUpdate2, true);
  document.getElementById(UKMode ? "att_x" : "targetX").
    addEventListener("keypress", InitMarchUpdate2, true);
  document.getElementById(UKMode ? "att_y" : "targetY").
    addEventListener("keypress", InitMarchUpdate2, true);
  NewNode = document.createElement("div");
  NewNode.setAttribute("id", "GM_Timing");
  NewNode.setAttribute("class", "row");
  Node.parentNode.parentNode.parentNode.
    insertBefore(NewNode, Node.parentNode.parentNode.nextSibling);
  NameNode = document.createElement("span");
  NameNode.id = "GM_CastleName";
  Node = document.getElementById(UKMode ? "att_y" : "targetY").parentNode;
  Node.insertBefore(NameNode, Node.lastChild);
  ServerArriveNode = document.createElement("span");
  ServerArriveNode.id = "GM_ServerArrive";
  document.getElementById(UKMode ? "z" : "timeDirection").
    parentNode.appendChild(ServerArriveNode);
  var TempoNode = document.getElementById(UKMode ? "ppp" : "speed").
    parentNode.parentNode;
  var FormationNode =
    SelNodes.snapshotItem(0).parentNode.parentNode.parentNode;
  for (var N = 0; N < Options.length; N++) {
    Options[N].innerHTML = String(N) + " (" + String(100 - 10 * N) + "%)";
  }
  var InsertionPoint = document.getElementById(UKMode ? "ppp" : "speed").
    parentNode.parentNode.parentNode;
  InsertionPoint.insertBefore(FormationNode, InsertionPoint.firstChild);
  InsertionPoint.insertBefore(TempoNode, InsertionPoint.firstChild);
  InsertionPoint.insertBefore(ButtonNode, InsertionPoint.firstChild);
  if (DefaultSpeed != -1) {
    Options.selectedIndex = DefaultSpeed;
    if (! Found && window.name && DefaultFormationNum != "0") {
      alert(Translate("Default attack formation not found."));
    } else {
      ButtonNode.getElementsByTagName("input")[0].click();
    }
  }
}

/******************************************************************************/
// calculate times for march in progress; copy army to "clipboard"

function CalcCancelTime(Arrives, OneWay, ArrivesNode) {
  var Now = new Date();
  var CancelTime = Math.ceil(
    (Now.getTime() < Arrives.getTime()) *
    (OneWay - (Arrives.getTime() - Now.getTime())) / 1000
  );
  var Node = document.getElementById("GM_CancelNode");
  if (! Node) {
    ArrivesNode.nextSibling.innerHTML += "<br /><span id='GM_CancelNode></span>";
    Node = document.getElementById("GM_CancelNode");
  }
  Node.innerHTML = "(" + Translate("Cancel") + ": ";
  if (CancelTime) {
    Node.innerHTML += HMS(CancelTime) + ")";
  } else {
    Node.innerHTML += "-- )";
  }
}

var PageMatch = new RegExp(
  Host + "/info\\.php\\?what=pohod&posted_id=\\d+$", "i"
).test(window.location);
if (PageMatch) {
  var PatCoords = /(\d+):(\d+)/i;
  var Nodes = document.evaluate(
    "//div[@class='left']",
    document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  var Node;
  var Now = new Date();
  for (var N = 0; N < Nodes.snapshotLength; N++) {
    Node = Nodes.snapshotItem(N);
    switch (N) {
      case (3):
        var Arrives = LocalTime(Node.nextSibling.innerHTML);
        var ArrivesNode = Node;
        break;
      case (4):
        var Returns = LocalTime(Node.nextSibling.innerHTML); break;
      case (5):
        var Gold = Number(Node.nextSibling.innerHTML); break;
      case (6):
        var Iron = Number(Node.nextSibling.innerHTML); break;
      case (7):
        var Wood = Number(Node.nextSibling.innerHTML); break;
      case (8):
        var Food = Number(Node.nextSibling.innerHTML); break;
      case (10):
        var Cargo = Number(Node.nextSibling.innerHTML);
        var CargoNode = Node;
        break;
      case (1):
        var From = PatCoords.exec(Node.nextSibling.innerHTML).slice(1);
        var Temp =
          eval(GM_getValue(SavedHost + "_MapData_" + From[0] + "_" + From[1], ""));
        Temp ? Temp = Temp[4] : Temp = "";
        Node.nextSibling.innerHTML += " " + Temp;
        break;
      case (2):
        var To = PatCoords.exec(Node.nextSibling.innerHTML).slice(1);
        var Temp =
          eval(GM_getValue(SavedHost + "_MapData_" + To[0] + "_" + To[1], ""));
        Temp ? Temp = Temp[4] : Temp = "";
        Node.nextSibling.innerHTML += " " + Temp;
        break;
    }
  }
  var Distance = Dist(From, To, true);
  var OneWay = Returns.getTime() - Arrives.getTime();
  var EffSpeed = OneWay / (60000) / Distance;
  CalcCancelTime(Arrives, OneWay, ArrivesNode);
  window.setInterval(CalcCancelTime, 1000, Arrives, OneWay, ArrivesNode);

  OneWay = HMS(OneWay / 1000);
  var NewNode = document.createElement("div");
  NewNode.innerHTML =
    "<div class='row'>" +
    "<div class='left'>&rarr;</div><div class='right'>" +
    StripDecimals(Distance, 2) + " (" + Translate("Effective Speed") + " " +
    StripDecimals(EffSpeed, 2) + ")</div></div>" +
    "<div class='row'><div class='left'>" + Translate("One-Way") +
    ":</div><div class='right'>" + OneWay + "</div></div>";
  ArrivesNode.parentNode.insertBefore(NewNode, ArrivesNode);
  var PercentFull = Round(100 * (Gold + Iron + Wood + Food) / Cargo, 1);
  CargoNode.nextSibling.innerHTML += " (" + String(PercentFull) + "%)";

  // copy army
  Node = document.evaluate(
    "descendant::div[@class='textformbox'][position()=last()]/div",
    document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  var PatUnit = new RegExp(
    "<div class=\"left\">(.*?)(?:</p>)?</div>[\\s\\S]*?" +
      "<div class=\"right\">(\\d+)</div>",
    "i"
  );
  var ArmyArray = new Array(UnitMaxID + 1);
  for (var N = 0; N < Node.snapshotLength; N++) {
    var Match = PatUnit.exec(Node.snapshotItem(N).innerHTML);
    ArmyArray[Number(UnitNameToID[Match[1]].slice(1))] = Match[2];
  }
  var CopyButton = document.createElement("input");
  CopyButton.type = "button"; CopyButton.value = Translate("Copy");
  CopyButton.addEventListener(
    "click",
    function () {
      GM_setValue(SavePrefix + "_CopyArmy", uneval(ArmyArray));
    },
    true
  );
  var InsertionPoint = document.getElementsByTagName("h2");
  InsertionPoint = InsertionPoint[InsertionPoint.length - 1];
  InsertionPoint.parentNode.
    insertBefore(CopyButton, InsertionPoint.nextSibling);
}

/******************************************************************************/
// scavenge/data from spy report

// calculate stealable resources given amount
function CalcStoreGuard(Amt, Guard) {
  return Math.max(
    0, Amt - (1 - StealFrac) * GuardLevels[Number(Guard)]
  );
}

// calculate stealable resources given resources
function CalcScavengeGuard(Report, Pat, Guard) {
  var Amt = Pat.exec(Report.innerHTML);
  Amt ? Amt = Number(Amt[1]) : Amt = 0;
  return CalcStoreGuard(Amt, Guard);
}

// return first match group or 0
function BuildingLevel(Report, Pat) {
  var Match = Pat.exec(Report.innerHTML);
  if (Match) {
    return Match[1];
  } else {
    return "0";
  }
}

function CopySpyUnits(Event) {
  var PatUnits = new RegExp(
    "<b>" + Event.target.previousSibling.innerHTML +
      "</b><input[^>]+>([\\s\\S]*?)((?:<b>)|(?:<br>.+?:<br>)|$)",
    "i"
  );
  var Units = PatUnits.exec(Event.target.parentNode.innerHTML)[1];
  ParseClipboardUnits(Units);
}

// saved message management
function SaveMessage(Event) {
  var Node = document.evaluate(
    "ancestor::*[@class='box message']",
    Event.target, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
  ).singleNodeValue;
  var SavedMessages =
    eval(GM_getValue(SavePrefix + "Messages", "[]"));
  var Found = false;
  for (var N = 0; N < SavedMessages.length; N++) {
    if (SavedMessages[N] == Node.getAttribute("GM_OrigHTML")) {
      Found = true; break;
    }
  }
  if (! Found) SavedMessages.push(Node.getAttribute("GM_OrigHTML"));
  GM_setValue(SavePrefix + "Messages", uneval(SavedMessages));
}

function DisplayMessages() {
  var DelNode = document.getElementById("deleteMessages");
  var DelText =
    GM_getValue(SavePrefix + "DelMsgNodes", "");
  if (! DelNode && DelText) {
    DelNode = document.createElement("div");
    document.getElementById("content").appendChild(DelNode);
    DelNode.innerHTML = DelText; DelNode.id = "deleteMessages";
  }
  var Nodes = document.evaluate(
    "//div[@class='infosmall']",
    document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  for (var N = 0; N < Nodes.snapshotLength; N++) {
    Nodes.snapshotItem(N).parentNode.removeChild(Nodes.snapshotItem(N));
  }
  Nodes = document.getElementsByClassName("messageWrapper");
  while (Nodes.length) {
    Nodes[0].parentNode.removeChild(Nodes[0]);
  }
  Nodes = document.getElementsByClassName("paging");
  for (var N = 0; Nodes.length > 0; N++) {
    Nodes[0].parentNode.removeChild(Nodes[0]);
  }
  Nodes = document.getElementsByClassName("folders")[0].
    getElementsByTagName("li");
  for (var N = 0; N < Nodes.length; N++) {
    RemoveClass(Nodes[N], "active");
  }
  DelNode = document.getElementById("GM_DeletePage");
  if (DelNode) DelNode.parentNode.removeChild(DelNode);
  AddClass(document.getElementById("GM_SavedMessages"), "active");
  DelNode = document.evaluate(
    "//a[contains(@href,'delall=yes')]",
    document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
  ).singleNodeValue;
  if (DelNode) {
    DelNode.id = "GM_Del-2";
    DelNode.href = "javascript:void(0);";
    DelNode.addEventListener("click", DeleteMessage, true);
  }
  DelNode = document.evaluate(
    "//a[contains(@href,'document.delMsgs.submit()')]",
    document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
  ).singleNodeValue;
  if (DelNode) {
    DelNode.id = "GM_Del-1";
    DelNode.href = "javascript:void(0);";
    DelNode.setAttribute("onclick", "");
    DelNode.addEventListener("click", DeleteMessage, true);
  }
  var InsertionPoint = document.getElementById("GM_UsefulLinks");
  var SavedMessages =
    eval(GM_getValue(SavePrefix + "Messages", "[]"));
  
  for (var N = 0; N < SavedMessages.length; N++) {
    var NewNode = document.createElement("div");
    NewNode.setAttribute("class", "messageWrapper");
    NewNode.innerHTML = SavedMessages[N];
    InsertionPoint.parentNode.insertBefore(NewNode, InsertionPoint.nextSibling);
    FormatSpyReport(NewNode, true);
    DelNode = document.evaluate(
      "descendant::a[@id='delete']",
      NewNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
    ).singleNodeValue;
    if (DelNode) {
      DelNode.href = "javascript:void(0);";
      DelNode.id = "GM_Del" + String(N);
      DelNode.addEventListener("click", DeleteMessage, true);
    }
  }
}

function DeleteMessage(Event) {
  var ID = Number(Event.target.id.slice(6));
  var SavedMessages =
    eval(GM_getValue(SavePrefix + "Messages", "[]"));
  if (ID >= 0) {
    SavedMessages.splice(ID, 1);
  } else if (ID == -1) {
    var Nodes = document.getElementsByClassName("messageWrapper");
    var NewMessages = [];
    for (var N = 0; N < Nodes.length; N++) {
      var Checked = document.evaluate(
        "descendant::input[@name='msg_delete[]']",
        Nodes[N], null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
      ).singleNodeValue.checked;
      if (! Checked) NewMessages.push(SavedMessages[Nodes.length - 1 - N]);
    }
    SavedMessages = NewMessages;
    SavedMessages.reverse();
  } else if (ID == -2) {
    SavedMessages = [];
  }
  GM_setValue(SavePrefix + "Messages", uneval(SavedMessages));
  DisplayMessages();
}

function FormatSpyReport(Report, SavedLocal) {
  var OrigHTML = Report.innerHTML;
  var PatName = /<p>.+(?:<br>\s*)+([\s\S]+?[^\s]+)\s*(?:&nbsp;)/i;
  var Name = BuildingLevel(Report, PatName);
  var PatCoords = /\[(\d+):(\d+)\]/i;
  var PatCoordsG = /(?:<a[^>]*>)?\[(\d+):(\d+)\](?:<\/a>)?/ig;
  var PatGold = new RegExp(Translate("Gold") + ":" + " (\\d+)\\s*(<br>)?", "i");
  var PatIron = new RegExp(Translate("Iron") + ":" + " (\\d+)\\s*(<br>)?", "i");
  var PatWood = new RegExp(Translate("Wood") + ":" + " (\\d+)\\s*(<br>)?", "i");
  var PatFood = new RegExp(Translate("Food") + ":" + " (\\d+)\\s*(<br>)?", "i");
  var PatGoldProd = new RegExp(Translate("Gold Mine") + " - (\\d+)", "i");
  var PatIronProd = new RegExp(Translate("Iron Mine") + " - (\\d+)", "i");
  var PatWoodProd = new RegExp(Translate("Lumbermill") + " - (\\d+)", "i");
  var PatFoodProd = new RegExp(Translate("Farms") + " - (\\d+)", "i");
  var PatWall = new RegExp(Translate("Wall") + " - (\\d+)", "i");
  var PatHomes = new RegExp(Translate("Homes") + " - (\\d+)", "i");
  var PatOrder = new RegExp(Translate("Order") + " - (\\d+)", "i");
  var PatBarracks = new RegExp(Translate("Barracks") + " - (\\d+)", "i");
  var PatStables = new RegExp(Translate("Stables") + " - (\\d+)", "i");
  var PatGuild = new RegExp(Translate("Guild") + " - (\\d+)", "i");
  var PatGuard = new RegExp(Translate("Guard Station") + " - (\\d+)", "i");
  var PatStore = new RegExp(Translate("Store House") + " - (\\d+)", "i");
  var PatBuildings = /(<b>.+?<\/b>[\s\S]*?)<\/p>/i;
  var Now = new Date();
  var DateStamp = String(Now.getFullYear()) +
    Zero(String(Now.getMonth() + 1), 2) + Zero(String(Now.getDate()), 2);

  // relink coords to march page
  Report.innerHTML = Report.innerHTML.replace(
    PatCoordsG,
    function () {
      var Names = GetCastleName(arguments);
      var Start = "";
      if (Names[0].length) {
        Start = Names[0] + "--";
      } else if (Names[1].length) {
        if (! PatName.test(Report.innerHTML))
          Start = Names[1][1];
        Start += " (" + Names[1][0] + ") ";
      }
      return Start + arguments[0] + "&nbsp;(<a href='" +
        Host + "/pohod.php?attackx=" +
        arguments[1] + "&attacky=" + arguments[2] +"'>&clubs;</a>)";
    }
  );

  // highlight coords
  Report.innerHTML = Report.innerHTML.replace(
    PatCoordsG,
    function () {
      return "<span style='background-color: black'>" + arguments[0] +
        "</span>";
    }
  );

  var TimeNode = Report.getElementsByClassName("details")[0];
  var ReportTime = LocalTime(TimeNode.innerHTML);
  TimeNode.innerHTML = "<small>" + DateFormat(ReportTime, true) + "</small>";
  var Seconds = Math.ceil((Now.getTime() - ReportTime.getTime()) / 1000);
  var Colour = "";
  if (Seconds <= 14400) {
    Colour = "";
  } else if (Seconds <= 43200) {
    Colour = "lightgreen";
  } else if (Seconds <= 86400) {
    Colour = "orange";
  } else {
    Colour = "red";
  }
  Report = Report.getElementsByClassName("box message")[0];
  Report.setAttribute("GM_OrigHTML", OrigHTML);
  Report.innerHTML =
    (Colour ? "<span style='background-color: " + Colour + "'>": "<span>") +
    Translate("Message Age") + ": " + HMS(Seconds) + "</span><br />" + Report.innerHTML;
  // only spy reports have forward.php; only format as a spy report if this link is found
  var HasForward = document.evaluate(
    "descendant::a[starts-with(@href,'forward.php')]",
    Report, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
  ).singleNodeValue;
  if (HasForward) {
    var Coords = [
      PatCoords.exec(Report.innerHTML)[1], PatCoords.exec(Report.innerHTML)[2]
    ];
    var GoldProd = BuildingLevel(Report, PatGoldProd);
    var IronProd = BuildingLevel(Report, PatIronProd);
    var WoodProd = BuildingLevel(Report, PatWoodProd);
    var FoodProd = BuildingLevel(Report, PatFoodProd);
    var MaxProd = [GoldProd, IronProd, WoodProd, FoodProd];
    MaxProd.sort(CompNum).reverse();
    MaxProd = MaxProd[0];
    var Wall = BuildingLevel(Report, PatWall);
    var Homes = BuildingLevel(Report, PatHomes);
    var Order = BuildingLevel(Report, PatOrder);
    var Barracks = BuildingLevel(Report, PatBarracks);
    var Stables = BuildingLevel(Report, PatStables);
    var Guild = BuildingLevel(Report, PatGuild);
    var MaxTrain = [Barracks, Stables, Guild];
    MaxTrain.sort(CompNum).reverse();
    MaxTrain = MaxTrain[0];
    var Guard = BuildingLevel(Report, PatGuard);
    var Store = BuildingLevel(Report, PatStore);
    var GoldAmt = CalcScavengeGuard(Report, PatGold, Guard);
    var IronAmt = CalcScavengeGuard(Report, PatIron, Guard);
    var WoodAmt = CalcScavengeGuard(Report, PatWood, Guard);
    var FoodAmt = CalcScavengeGuard(Report, PatFood, Guard);
    var Match = PatGold.exec(Report.innerHTML);
    if (Match && ! Match[2]) {
      Report.innerHTML = Report.innerHTML.replace(
        /(?:<br>)*(<b>)/ig, "<br /><br />$1"
      )
      Report.innerHTML = Report.innerHTML.replace(
        /(<\/b>)(?:<br>)*/ig, "$1<br />"
      )
      function BreakRes(Match) {return Match + "<br />";}
      Report.innerHTML = Report.innerHTML.replace(PatGold, BreakRes);
      Report.innerHTML = Report.innerHTML.replace(PatIron, BreakRes);
      Report.innerHTML = Report.innerHTML.replace(PatWood, BreakRes);
      Report.innerHTML = Report.innerHTML.replace(PatFood, BreakRes);
    }
    var Total = GoldAmt + IronAmt + WoodAmt + FoodAmt;
    var MaxTotal = 4 * CalcStoreGuard(StoreLevels[Number(Store)], Guard);
    var CastleDists = CalcCastleDists(Coords);

    Report.innerHTML = Report.innerHTML.replace(
      PatBuildings,
      "<table><tr id='GM_Buildings" + String(N) + "'><td><span id='GM_Ignore" +
      String(N) + "'>" + CastleDists[0] + "</span>" +
      "<br /><a href=\"" + Host + "/pohod.php?attackx=" +
      String(Coords[0]) + "&attacky=" + String(Coords[1]) + "\">&clubs;</a> " +
      "<a href=\"" + Host + "/map.php?setx=" +
      Coords[0] + "&sety=" + Coords[1] + "\">" + "&rarr;</a><br />" +
      "<input type='text' value='" +
      [
        "&apos;" + DateStamp,
        "&apos;" + Name, Coords[0], Coords[1],
        String(MaxProd), Wall, Guard,
        Store, String(Total), String(Total), String(Math.max(MaxTotal, Total)),
        String(Homes), String(Order), String(MaxTrain)
      ].join("\t") + "'><br />" +
      "<input type='text' value='" +
      [
        "&apos;" + DateStamp,
        "&apos;" + Name, Coords[0], Coords[1],
        String(MaxProd), Wall, Guard, Store, String(Total)
      ].join("\t") + "'><br />" +
      Translate("Total Res") + ": " + String(Total) + "/" + String(MaxTotal) +
      "<br /><br />" + "$1</td></tr></table>"
    );
    if (Match) {
      var NewNode = document.createElement("td");
      var SpeedText = "";
      for (var M = 0; M < Speeds.length; M++) {
        var Speed = Speeds[M];
        SpeedText += Translate("Speed") + " " +
          String(Speed[0]) + " (" + String(Speed[1].join(", ")) + ")<br />" +
          MarchTime(Dist(Coords), Speed[0], " ", [0, 9], false, false) + "<br />";
      }
      NewNode.innerHTML =
        "<small>" + SpeedText + "</small>";
      document.getElementById("GM_Buildings" + String(N)).appendChild(NewNode);
    }

    // add facility to copy units/supports if observed
    // units only shown if enough QWs survive, so the bolded items are
    // always in order
    [["Units", "4"], ["Supports", "5"]].forEach(
      function (Option) {
        var InsertionPoint = document.evaluate(
          "descendant::span[starts-with(@id,'GM_Ignore')]/following-sibling::b[" + Option[1] + "]",
          Report, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
        ).singleNodeValue;
        if (InsertionPoint) {
          var CopyButton = document.createElement("input");
          CopyButton.type = "button"; CopyButton.value = Translate("Copy");
          CopyButton.id = Option;
          CopyButton.addEventListener("click", CopySpyUnits, true);
          InsertionPoint.parentNode.
            insertBefore(CopyButton, InsertionPoint.nextSibling);
        }
      }
    );
  }

  if (! SavedLocal) {
    var NewNode = document.createElement("span");
    NewNode.innerHTML = "<a href='javascript:void(0);'>Save</a>";
    NewNode.addEventListener("click", SaveMessage, true);
    var TempNode = Report.getElementsByTagName("p");
    TempNode = TempNode[TempNode.length - 1];
    TempNode.appendChild(NewNode);
  }
}

// calculate max possible scavenge based on guard station; march times
var PageMatch = new RegExp(
  Host + "/(?:news|writenote)\\.php(?:.*?(?:type=(\\d+)))?",
  "i"
).exec(window.location);
if (PageMatch) {
  var DelNode = document.getElementById("deleteMessages");
  if (DelNode) {
    GM_setValue(
      SavePrefix + "DelMsgNodes",
        DelNode.innerHTML
    );
  }
  var Paging = document.evaluate(
    "//p[@class='paging']", document, null,
    XPathResult.FIRST_ORDERED_NODE_TYPE, null
  ).singleNodeValue;
  if (Paging) {
    var NewNode = document.createElement("p");
    NewNode.innerHTML = Paging.innerHTML;
    NewNode.setAttribute("class", "paging"); NewNode.id = "GM_Paging";
    var Content = document.getElementById("content");
    Content.insertBefore(NewNode, Content.firstChild);
  }
  var InsertionPoint = document.getElementsByClassName("sent")[0];
  var NewNode = document.createElement("li");
  NewNode.id = "GM_SavedMessages"; NewNode.setAttribute("class", "sent");
  NewNode.innerHTML =
    "<a href='javascript:void(0);'>" + Translate("Saved") + "</a>";
  NewNode.addEventListener("click", DisplayMessages, true);
  InsertionPoint.parentNode.insertBefore(NewNode, InsertionPoint.nextSibling);

  var Reports = document.evaluate(
    "//div[@class='messageWrapper']", document, null,
    XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  for (var N = 0; N < Reports.snapshotLength; N++) {
    var Curr = Reports.snapshotItem(N);
    var Node = document.evaluate(
      "preceding-sibling::h2[1]",
      Curr, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
    ).singleNodeValue;
    if (Node) Curr.insertBefore(Node, Curr.firstChild);
    FormatSpyReport(Curr);
  }

  Node = document.getElementById("deleteMessages");
  if (Node) {
    var NewNode = document.createElement("a");
    NewNode.href = "javascript:void(0);"; NewNode.id = "GM_DeletePage";
    NewNode.innerHTML = Translate("Delete Page");
    NewNode.addEventListener(
      "click",
      function () {
        var Nodes = document.evaluate(
          "//span[@class='markMsg']/input[@type='checkbox']",
          document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
        );
        for (var N = 0; N < Nodes.snapshotLength; N++) {
          Nodes.snapshotItem(N).checked = true;
        }
        document.evaluate(
          "//form[@name='delMsgs']",
          document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
        ).singleNodeValue.submit();
      },
      true
    );
    Node.appendChild(NewNode);
  }

  switch(PageMatch[1]) {
    // autolink coords in personal messages
    case (UKMode ? "1" : "6"):
      var TextCoords = document.evaluate(
        "//div[@class='box message']/" +
          "descendant-or-self::*[contains(text(), ':')]",
        document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
      );
      var PatBareCoord = /([.\s\(\[,;-])(\d+):(\d+)([.\s\)\],;-])/i;
      for (var N = 0; N < TextCoords.snapshotLength; N++) {
        var Curr = TextCoords.snapshotItem(N);
        var Match = PatBareCoord.exec(Curr.innerHTML);
        var LinkAncestor = document.evaluate(
          "ancestor-or-self::a",
          Curr, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
        ).singleNodeValue;
        if (Match && ! LinkAncestor) {
          Curr.innerHTML = Curr.innerHTML.replace(
            PatBareCoord,
            "$1<span style='background-color: black'>" +
              "<a href='map.php?setx=$2&sety=$3'>$2:$3</a></span> " +
              "(<a href='pohod.php?attackx=$2&attacky=$3'>&clubs;</a>)$4"
          );
        }
      }
      break;
    // battle reports
    case ("2"):
      function CallBR(Doc, N) {
        var Match = Doc.evaluate(
          "//ns:span[@class='cleanupGold']/ancestor::ns:li",
          Doc, nsResolver, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null
        );
        var TotalScavenge = TotalStolen = 0, Current;
        for (let N = 0; N < Match.snapshotLength; N++) {
          Current = 0;
          for (let M = 0; M < 4; M++) {
            Current += Number(
              Match.snapshotItem(N).getElementsByTagName("span")[M].innerHTML
            );
          }
          if (
            Match.snapshotItem(N).getElementsByClassName("cleanupPopulation").length
          ) {
            TotalScavenge += Current;
          } else {
            TotalStolen += Current;
          }
        }
        var TotalCapacity = 0;
        var LastAttack = Doc.evaluate(
          "//ns:div[@class='armyOrder attacker']",
          Doc, nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
        );
        LastAttack = LastAttack.snapshotItem(LastAttack.snapshotLength - 1);
        var UnitsLeft = Doc.evaluate(
          "descendant::ns:a[contains(@id,'unitPicSmall')]",
          LastAttack, nsResolver, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null
        );
        var PatID = /unitPicSmall-(\d+)/i, PatAlive = /(\d+)\s*\//i;
        for (let N = 0; N < UnitsLeft.snapshotLength; N++) {
          TotalCapacity +=
            Units["u" + PatID.exec(UnitsLeft.snapshotItem(N).id)[1]]["carry"] *
              Number(PatAlive.exec(UnitsLeft.snapshotItem(N).innerHTML)[1]);
        }
        var AttackerText = Doc.getElementsByClassName("box round")[0].
          getElementsByTagName("h3")[0].innerHTML;
        var AttackingCastle = Doc.evaluate(
          "//ns:div[@id='" + AttackerText + "Info'][1]/descendant::ns:div[@class='details']/" +
            "descendant::ns:a[last()]",
          Doc, nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null
        ).singleNodeValue.innerHTML.split(":");
        AttackingCastle.unshift(AttackingCastle[0] + ":" + AttackingCastle[1]);
        var NewNode = document.createElement("span");
        NewNode.innerHTML = "<br />" +
          AttackerText + ": <span style='background-color: black'><a href=\"" + Host +
          "/map.php?setx=" +
          AttackingCastle[1] + "&sety=" + AttackingCastle[2] + "\">" +
          "[" + AttackingCastle[1] + ":" + AttackingCastle[2] + "]</a>" +
          "</span><span id='GM_PlaceCastleName" + String(N) + "'></span>" +
          "<br />" + Translate("Scavenge") + ": " +
          String(TotalScavenge) +
          "<br />" + Translate("Survivor Capacity") + ": " + String(TotalStolen) + "/" +
          String(TotalCapacity) + " (" +
          String(
            StripDecimals(Round(100 * TotalStolen / TotalCapacity, 0.01), 2)
          ) +
          "%)";
        var Node = document.getElementById("GM_Msg" + String(N));
        Node.parentNode.insertBefore(NewNode, Node.nextSibling);
        var Name =
          GetCastleName(AttackingCastle, "GM_PlaceCastleName" + String(N));
      }

      var BattleReports = document.evaluate(
        "//div[@class='box message']",
        document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
      );
      for (var N = 0; N < BattleReports.snapshotLength; N++) {
        BattleReports.snapshotItem(N).innerHTML = BattleReports.snapshotItem(N).innerHTML.
          replace(/(<br>){2,}/ig, "<br />");
        var URL = document.evaluate(
          "descendant::a[1]",
          BattleReports.snapshotItem(N), null,
          XPathResult.FIRST_ORDERED_NODE_TYPE, null
        ).singleNodeValue;
        if (URL) {
          URL.id = "GM_Msg" + String(N);
          GetDoc(URL.href, CallBR, N);
        }
      }
      break;
  }
}

/******************************************************************************/
// scavenging fields

var PageMatch = new RegExp(Host + "/vip_poleta\\.php", "i").
  test(window.location);
if (PageMatch) {
  var PatCoords = /\[(\d+):(\d+)\]/i;
  var PatResources = new RegExp(
    "<strong>(\\d+)</strong> " + Translate("Gold") + "[\\s\\S]*" +
      "<strong>(\\d+)</strong> " + Translate("Iron") + "[\\s\\S]*" +
      "<strong>(\\d+)</strong> " + Translate("Wood") + "[\\s\\S]*" +
      "<strong>(\\d+)</strong> " + Translate("Food") + "[\\s\\S]*" +
      "<strong>(\\d+)</strong> " + Translate("People") + "[\\s\\S]*",
    "i"
  );
  var SelNodes = document.evaluate(
    "//tbody/tr", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  for (var N = 0; N < SelNodes.snapshotLength; N++) {
    var Coords = PatCoords.exec(SelNodes.snapshotItem(N).innerHTML);
    Coords = [Coords[1], Coords[2]];
    var Speed =
      Math.round(Units["u10"]["speed"] / (1 + ScoutLevels[ScoutLevel] / 100));
    var MarchString = MarchTime(Dist(Coords), Speed, " ", [0], false, false);
    var Resources = PatResources.exec(SelNodes.snapshotItem(N).innerHTML);
    var Pop = Number(Resources[5]);
    Resources = Number(Resources[1]) + Number(Resources[2]) +
      Number(Resources[3]) + Number(Resources[4]);
    var Colour = "";
    if (Number(Pop) >= HealThreshold || Resources >= ScavengeThreshold) {
      Colour = "lightgreen";
    }
    var CarryString = (Colour ? "<font color='" + Colour + "'>" : "") +
      Translate("Total Res") + ": " + String(Resources) + "; " +
      String(Math.ceil(Resources / Units["u10"]["carry"])) + " " +
      Translate("LC") + "/" +
      String(Math.ceil(Resources / Units["u19"]["carry"])) + " " +
      Translate("CA") + (Colour ? "</font>" : "");
    var NewNode = document.createElement("td");
    NewNode.setAttribute("class", "special");
    NewNode.innerHTML = CarryString + "<br />" + MarchString;
    SelNodes.snapshotItem(N).appendChild(NewNode);
    SelNodes.snapshotItem(N).firstChild.nextSibling.innerHTML +=
      "<br />(<a href=\"" + Host + "/map.php?setx=" +
      Coords[0] + "&sety=" + Coords[1] + "\">" + "&rarr;</a>)"
  }
}

/******************************************************************************/
// merchant hiding

// handler for merchant hiding
function CalcTime(Event) {
  // calculate room left given data in put boxes
  function RoomLeft() {
    var Used = Number(GoldNode.value) + Number(IronNode.value) +
        Number(WoodNode.value) + Number(FoodNode.value);
    var Left = Limit - Used;
    document.getElementById("GM_Left").innerHTML = String(Left);
    document.getElementById("total").innerHTML = String(Used);
    return Left;
  }

  // top up a given resource
  function TopUp(Event) {
    var TargetText = Event.target.parentNode.parentNode.firstChild;
    var AvailableRes;
    switch (TargetText.id) {
      case ("sendg"): AvailableRes = GlobalGold - Number(GoldNode.value); break;
      case ("sendj"): AvailableRes = GlobalIron - Number(IronNode.value); break;
      case ("sendd"): AvailableRes = GlobalWood - Number(WoodNode.value); break;
      case ("sendh"): AvailableRes = GlobalFood - Number(FoodNode.value); break;
    }
    AvailableRes = Math.max(0, AvailableRes);
    var Total = Number(document.getElementById("total").innerHTML);
    var Room = Math.max(0, Limit - Total);
    TargetText.focus();
    TargetText.value =
      String(Number(TargetText.value) + Math.min(Room, AvailableRes));
    TargetText.blur();
    RoomLeft();
  }

  // use up merchant space recursively
  function DivideEvenly(Event) {
    var Left = Math.max(0, RoomLeft());
    var FreeNodes = (GoldAvailable > 0) + (IronAvailable > 0) +
      (WoodAvailable > 0) + (FoodAvailable > 0);
    while (Left > 0) {
      var GoldAvailable = GlobalGold - Number(GoldNode.value);
      var IronAvailable = GlobalIron - Number(IronNode.value);
      var WoodAvailable = GlobalWood - Number(WoodNode.value);
      var FoodAvailable = GlobalFood - Number(FoodNode.value);
      FreeNodes = (GoldAvailable > 0) + (IronAvailable > 0) +
        (WoodAvailable > 0) + (FoodAvailable > 0);
      if (FreeNodes == 0) break;
      var Even = Math.floor(Left / FreeNodes);
      ResList.forEach(
        function (Option) {
          var ResName = Option[0];
          if (eval(ResName + "Available > 0")) {
            eval(
              ResName + "Node.value = String(Number(" + ResName +
                "Node.value) + Math.min(Even, " + ResName + "Available));"
            );
          }
        }
      );
      Left = RoomLeft();
      if (Left < 4) {
        ResList.forEach(
          function (Option) {
            var ResName = Option[0];
            if (eval(ResName + "Available > 0 && Left")) {
              eval(
                ResName + "Node.value = String(Number(" + ResName +
                  "Node.value) + 1);"
              );
              Left = RoomLeft();
            }
          }
        );
      }
    }
  }

  // save/delete/load merchant targets
  function ManageMerchantTargets(Event) {
    var MerchantTargets =
      eval(GM_getValue(SavePrefix + "MerchantTargets", "[]"));
    if (! Event) {
      var NewNode = document.getElementById("GM_MerchantTargets");
      NewNode.innerHTML = "<option value='0:0'></option>";
      if (MerchantTargets.length) {
        MerchantTargets = eval(MerchantTargets);
        for (var N = 0; N < MerchantTargets.length; N++) {
          var Current = MerchantTargets[N];
          var CastleName = GetCastleName([null, Current[0], Current[1]]);
          if (CastleName[1].length) {
            CastleName = CastleName[1][1] + " (" + CastleName[1][0] + ")";
          } else {
            CastleName = "";
          }
          NewNode.innerHTML += "<option value='" + Current.join(":") + "'>" +
            CastleName + " (" + Current.join(":") + ")";
        }
      }
      NewNode.addEventListener("change", ManageMerchantTargets, true);
    } else {
      var Curr = [
        document.getElementById("sendx").value,
        document.getElementById("sendy").value
      ];
      if (Event.target.type == "select-one") {
        var Coords =
          Event.target.options[Event.target.selectedIndex].value.split(":");
        document.getElementById("sendx").value = Coords[0];
        document.getElementById("sendy").value = Coords[1];
        if (Coords[0] != "0" && Coords[1] != "0")
          document.getElementById("unload").checked = false;
      } else {
        var Found = false;
        for (var N = 0; N < MerchantTargets.length; N++) {
          Found = (
            Curr[0] == MerchantTargets[N][0] && Curr[1] == MerchantTargets[N][1]
          );
          if (Found) break;
        }
        if (! Found && Event.target.value == Translate("Save")) {
          MerchantTargets.push(Curr);
          GM_setValue(
            SavePrefix + "MerchantTargets",
            uneval(MerchantTargets)
          );
        }
        if (Found && Event.target.value == Translate("Delete")) {
          MerchantTargets.splice(N, 1);
          GM_setValue(
            SavePrefix + "MerchantTargets",
            uneval(MerchantTargets)
          );
        }
        ManageMerchantTargets();
      }
      CalcTime();
    }
  }

  var Limit = Number(document.evaluate(
    "//div[@class='textformbox']/div[2]/div[2]",
    document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
  ).singleNodeValue.innerHTML);
  var Node, NewNode, TimeNode;
  TimeNode = document.getElementById("GM_TimeNode");
  var ToX = document.getElementById("sendx").value,
      ToY = document.getElementById("sendy").value,
      GoldNode, IronNode, WoodNode, FoodNode,
      SendGold, SendIron, SendWood, SendFood;
  ResList.forEach(
    function (Option) {
      var ResName = Option[0];
      eval(
        ResName + "Node = document.getElementById('send" +
          Option[1] + "');"
      );
      eval(
        "Send" + ResName + " = Number(" + ResName + "Node.value);"
      );
    }
  );
  var SendTotal = SendGold + SendIron + SendWood + SendFood;
  var Resources = GlobalGold + GlobalIron + GlobalWood + GlobalFood;
  var Extra = Math.max(0, Resources - Limit);

  if (! TimeNode) {
    // create new nodes
    Node = document.getElementById("unload");
    TimeNode = document.createElement("div");
    TimeNode.setAttribute("id", "GM_TimeNode");
    Node.parentNode.insertBefore(TimeNode, Node.nextSibling);
    ResList.forEach(
      function (Option) {
        var ResName = Option[0];
        var TopUpNode = document.createElement("span");
        TopUpNode.innerHTML = " <a href='javascript:void(0)'>" +
          Translate("Top Up") + "</a>";
        TopUpNode.addEventListener("click", TopUp, true);
        eval(
          ResName + "Node.parentNode.insertBefore(TopUpNode, " +
            ResName + "Node.nextSibling);"
        );
      }
    );

    Node = document.evaluate(
      "//form[@name='send']/div[@class='right'][6]",
      document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
    ).singleNodeValue;
    NewNode = document.createElement("span");
    NewNode.innerHTML = "<br /><a href='javascript:void(0);'>" +
      Translate("Distribute Evenly") + "</a><br />";
    NewNode.addEventListener("click", DivideEvenly, true);
    Node.appendChild(NewNode);
    NewNode = document.createElement("div");
    NewNode.class = "left";
    NewNode.innerHTML = "<a href='javascript:void(0);'>" +
      Translate("Reset") + "</a>";
    NewNode.addEventListener(
      "click", function () {GoldNode.form.reset(); RoomLeft();}, true
    );
    Node.appendChild(NewNode);
    NewNode = document.createElement("div");
    NewNode.class = "left";
    NewNode.innerHTML = "<label>" + Translate("Room Left") +
      ":</label> <span id='GM_Left'></span>";
    Node = document.getElementById("total");
    Node.parentNode.insertBefore(NewNode, Node.nextSibling);
    NewNode = document.createElement("span");
    NewNode.id = "GM_CastleName";
    var InsertionPoint =
      (document.forms.namedItem("send").elements.namedItem("fastNav") ||
        document.getElementById("sendy")).parentNode;
    InsertionPoint.parentNode.insertBefore(NewNode, InsertionPoint.nextSibling);
    NewNode = document.createElement("input");
    NewNode.type = "button"; NewNode.value = "Save";
    NewNode.addEventListener("click", ManageMerchantTargets, true);
    InsertionPoint.parentNode.insertBefore(NewNode, InsertionPoint.nextSibling);
    NewNode = document.createElement("input");
    NewNode.type = "button"; NewNode.value = "Delete";
    NewNode.addEventListener("click", ManageMerchantTargets, true);
    InsertionPoint.parentNode.insertBefore(NewNode, InsertionPoint.nextSibling);
    InsertionPoint =
      document.forms.namedItem("send").elements.namedItem("fastNav") ||
        document.getElementById("sendy").parentNode;
    NewNode = document.createElement("select");
    NewNode.id = "GM_MerchantTargets";
    InsertionPoint.parentNode.insertBefore(NewNode, InsertionPoint.nextSibling);
    ManageMerchantTargets();
  }
  RoomLeft();
  var Name = GetCastleName([null, ToX, ToY]);
  NewNode = document.getElementById("GM_CastleName");
  NewNode.innerHTML = " " +
    (Name[0].length
       ? " (" + Name[0] + ")"
       : (Name[1].length ? " (" + Name[1].join(", ") + ")" : ""));
  TimeNode.innerHTML =
    MarchTime(
      Dist([ToX, ToY]), MerchantLevels[MerchantLevel], " ", [0], false, false
    ) +
    String(Resources) + "/" + String(Limit) + " (" + String(Extra) + " " +
    Translate("Extra") + ")";
}

var PageMatch = new RegExp(Host + "/market\\.php\\?action=2", "i").
  test(window.location);
if (PageMatch) {
  if (document.forms.namedItem("send").elements.namedItem("fastNav")) {
    var Head = document.getElementsByTagName("head")[0];
    var NewScript = document.createElement("script");
    // set coords from pulldown
    function setCoords(Obj) {
      var Match = /(\d+):(\d+)/i.exec(document.forms.namedItem("send").
        elements.namedItem("fastNav")[Obj.selectedIndex].value);
      document.getElementById("sendx").value = Match[1];
      document.getElementById("sendx").focus();
      document.getElementById("sendy").value = Match[2];
      document.getElementById("sendx").blur();
    }
    NewScript.innerHTML = setCoords.toString();
    Head.appendChild(NewScript);

    var Options =
      document.forms.namedItem("send").elements.namedItem("fastNav").options;
    var PatCoords = /(\d+):(\d+)/i;
    for (var N = 0; N < Options.length; N++) {
      var Match = PatCoords.exec(Options[N].value);
      for (var M = 0; M < PlayerCastles.length; M++) {
        if (PlayerCastles[M][2] == Match[1] && PlayerCastles[M][3] == Match[2]) {
          Options[N].innerHTML = PlayerCastles[M][0]; break;
        }
      }
    }
  }

  var ToX = document.getElementById("sendx").value;
  var ToY = document.getElementById("sendy").value;
  document.getElementById("sendx").
    addEventListener("blur", CalcTime, true);
  document.getElementById("sendy").
    addEventListener("blur", CalcTime, true);
  ResList.forEach(
    function (Option) {
      document.getElementById("send" + Option[1]).
        addEventListener("change", CalcTime, true);
    }
  );
  var OwnCastle = false;
  for (var N = 0; N < PlayerCastles.length; N++) {
    if (ToX == PlayerCastles[N][2] && ToY == PlayerCastles[N][3]) {
      OwnCastle = true; break;
    }
  }
  document.getElementById("unload").checked = ! OwnCastle;
  var QuickNode = document.createElement("div");
  QuickNode.innerHTML = "<div class='left'><label>" + Translate("Quick Roads") +
    ": </label></div>" +
    "<div class='right'><input id='GM_QuickRoads' type='checkbox' /></div>";
  InsertionPoint = document.getElementById("unload");
  InsertionPoint.parentNode.
    insertBefore(QuickNode, InsertionPoint.nextSibling);
  document.getElementById("GM_QuickRoads").
    addEventListener("change", CalcTime, true);
  CalcTime();
  window.setInterval(CalcTime, 1000);
}

/******************************************************************************/
// identify profitable trades

var PageMatch = new RegExp(Host + "/market\\.php\\?(.*?)action=1(.*?)", "i").
  exec(window.location);
if (PageMatch) {
  var Offers = document.evaluate(
    "//div[@class='box'][3]/descendant::tr",
    document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  for (var N = 0; N < Offers.snapshotLength; N++) {
    if (N == 0) {
      var InsertionPoint = document.evaluate(
        "descendant::td[position()=last()-1]",
        Offers.snapshotItem(N), null, XPathResult.FIRST_ORDERED_NODE_TYPE,
        null
      ).singleNodeValue;
      var NewNode = document.createElement("td");
      NewNode.innerHTML = Translate("Profit Ratio");
      InsertionPoint.parentNode.
        insertBefore(NewNode, InsertionPoint.nextSibling);
    } else {
      var Resources = document.evaluate(
        "descendant::td[position()>last()-3]",
        Offers.snapshotItem(N), null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
        null
      );
      var PatNums = /(\d+)/ig;
      var Gives = Resources.snapshotItem(0).innerHTML;
      var GivesNum = 0;
      do {
        Match = PatNums.exec(Gives);
        if (Match) GivesNum += Number(Match[1]);
      } while (Match);
      var Receives = Resources.snapshotItem(1).innerHTML;
      var ReceivesNum = 0;
      do {
        Match = PatNums.exec(Receives);
        if (Match) ReceivesNum += Number(Match[1]);
      } while (Match);
      var ProfitRatio = StripDecimals(Round(GivesNum / ReceivesNum, 0.1), 1);
      var NewNode = document.createElement("td");
      NewNode.innerHTML = ProfitRatio + ", " + String(GivesNum - ReceivesNum);
      Offers.snapshotItem(N).
        insertBefore(NewNode, Resources.snapshotItem(1).nextSibling);
      Resources.snapshotItem(2).setAttribute("class", "special");
    }    
  }
}

/******************************************************************************/
// converter helper

function Converter(Event) {
  var Coefficient = document.evaluate(
    "//script[contains(text(), 'var factor = 0')]",
    document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
  ).singleNodeValue.innerHTML;
  Coefficient = Number(/var\s*factor\s*=\s*([\d.]*)/i.exec(Coefficient)[1]);
  var Target = document.evaluate(
    "preceding-sibling::input", Event.target, null,
    XPathResult.FIRST_ORDERED_NODE_TYPE, null
  ).singleNodeValue;
  var Source = document.evaluate(
    "//form[@name='form_new']/descendant::input[@type='text']",
    document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  for (var N = 0; N < Source.snapshotLength; N++) {
    if (Source.snapshotItem(N).value != "0") {
      var Want = Number(Source.snapshotItem(N).value);
      Source = Source.snapshotItem(N);
      break;
    }
  }
  Source.value = "0";
  Target.value = String(Math.ceil(Want / Coefficient));
  var Node = document.getElementById("z" + Source.id.slice(1))
  Node.checked = true; unsafeWindow.recalculateResources();
}

var PageMatch = new RegExp(Host + "/market\\.php\\?action=4", "i").
  exec(window.location);
if (PageMatch) {
  ResList.forEach(
    function (Option) {
      var ResName = Option[0];
      var SelNode = document.getElementById("p" + Option[1]);
      var NewNode = document.createElement("input");
      NewNode.type = "button";
      NewNode.value = Translate("Source");
      NewNode.addEventListener("click", Converter, true);
      SelNode.parentNode.insertBefore(NewNode, SelNode.nextSibling);
    }
  );
}

/******************************************************************************/
// battle report

// save skills from report
function CopySkills(Event) {
  GM_setValue(SavePrefix + "_CopySkills", Event.target.getAttribute("GM_Skills"));
}

// save army from report
function CopyArmy(Event) {
  GM_setValue(SavePrefix + "_CopyArmy", Event.target.getAttribute("GM_Army"));
}

// save observed battle formation to "clipboard"
function CopyFormation(Event) {
  var Nodes = document.evaluate(
    "following-sibling::div[starts-with(@class, 'armyOrder')][1]/" +
      "div[starts-with(@class, 'unit ')]",
    Event.target, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  var CopyFormation = new Array(UnitMaxID + 1);
  var UnitPat = /unitPicSmall-(\d+)/i;
  var PositionPat = /x(\d)y(\d)/i;
  var ID, Position;
  for (var N = 0; N < Nodes.snapshotLength; N++) {
    ID = Number(UnitPat.exec(Nodes.snapshotItem(N).firstChild.id)[1]);
    Position = PositionPat.exec(Nodes.snapshotItem(N).getAttribute("class"));
    Position = Position[1] + "_" + Position[2];
    CopyFormation[ID] = Position;
  }
  GM_setValue(SavePrefix + "_CopyFormation", uneval(CopyFormation));
}

// parse hero info
function ParseSkills(Node) {
  var Cells = Node.getElementsByTagName("tbody")[0].getElementsByTagName("td");
  var Skills = [], Skills2 = [];
  for (var N = 0; N < Cells.length; N += 3) {
    Skills[Cells[N].innerHTML] = Number(Cells[N + 1].innerHTML);
    Skills2.push(Cells[N + 1].innerHTML);
  }
  if (window.location.href.indexOf("battleSimulator=1") > -1) {
    var Level = /<\/strong>.*?(\d+)/i.
      exec(Node.getElementsByClassName("details")[0].innerHTML);
  } else {
    var Level = /<\/strong>.+<br\s*\/?>.*?(\d+)/i.
      exec(Node.getElementsByClassName("details")[0].innerHTML);
  }
  Skills["Level"] = (Level ? Level[1] : "0");
  Skills2 = [0].concat(Skills2);
  var SaveButton = document.createElement("input");
  SaveButton.type = "button"; SaveButton.value = Translate("Copy Skills");
  SaveButton.addEventListener("click", CopySkills, true);
  SaveButton.setAttribute("GM_Skills", uneval(Skills2));
  var InsertionPoint = document.evaluate(
    "descendant::table[1]",
    Node, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
  ).singleNodeValue;
  InsertionPoint.parentNode.insertBefore(SaveButton, InsertionPoint.nextSibling);
  return Skills;
}

// upgrade/loss calculations
function ParseUnits(Node, Skills, Phase1, End) {
  var AttackerText = document.getElementsByClassName("box round")[0].
    getElementsByTagName("h3")[0].innerHTML;
  var DefenderText = document.getElementsByClassName("box round")[0].
    getElementsByTagName("h3")[1].innerHTML;
  var SurvivorCarry = 0;
  var ResultNode = document.evaluate(
    "//div[@class='infosmall']",
    document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  if (Phase1) {
    ResultNode = ResultNode.snapshotItem(0);
  } else {
    ResultNode = ResultNode.snapshotItem(ResultNode.snapshotLength - 1);
  }
  var AttackerWin = new RegExp(AttackerText, "i").test(ResultNode.innerHTML);
  var Attacker = (Node.getAttribute("class") == "armyOrder attacker");
  if (! End) {
    var SpecialNode = document.getElementsByClassName("specialUnitsBox")
      [Phase1 ? 0 : 1];
    var SpecialInfantry = SpecialCavalry = SpecialDefender = SpecialTemp = 0;
    if (SpecialNode) {
      SpecialNode = SpecialNode.getElementsByClassName("side-" + [Attacker ? "attacker" : "defender"])[0];
      if (SpecialNode) {
        SpecialTemp = Number(
          SpecialNode.getElementsByTagName("td")[2].
            getElementsByTagName("strong")[0].innerHTML.slice(0, -1)
        );
        switch (
          SpecialNode.getElementsByTagName("td")[4].
            getElementsByTagName("strong")[0].innerHTML
        ) {
          case (Translate("Warrior"))   : SpecialInfantry = SpecialTemp; break;
          case (Translate("Cavalryman")): SpecialCavalry = SpecialTemp; break;
          case (Translate("Defender"))  : SpecialDefender = SpecialTemp; break;
        }
      }
    }
  }
  var TotalPop = 0;
  var LostGold = LostIron = LostWood = LostFood = LostPop = LostTotal =
      TotalLife = LostGold2 = LostIron2 = LostWood2 = LostFood2 = LostPop2 =
      LostTotal2 = 0;
  var PatAliveDead = /(\d+) \/ (\d+)/i;
  var PatData = /body=\[\s*([\s\S]+?)\s*\]/i;
  var DataNode = document.createElement("div"), DataNodes;
  var Army = Node.getElementsByTagName("div");
  var ArmyArray = new Array(UnitMaxID + 1);
  for (N = 0; N < Army.length; N++) {
    var Stats = Army[N].getAttribute("title").
      replace(/&gt;/ig, ">").replace(/&lt;/ig, "<");
    var ID = "u" +
      /unitPicSmall-(\d+)/i.exec(Army[N].firstChild.getAttribute("id"))[1];
    var AliveDead = Army[N].firstChild.innerHTML;
    DataNode.innerHTML = PatData.exec(Stats)[1];
    var DataNodes = DataNode.getElementsByTagName("dd"),
        Fled = DataNodes[2].innerHTML,
        Archer = DataNodes[3].innerHTML,
        Building = DataNodes[4].innerHTML,
        Cavalry = DataNodes[5].innerHTML,
        Infantry = DataNodes[6].innerHTML,
        Wizard = DataNodes[7].innerHTML,
        Life = DataNodes[8].innerHTML;
    // reverse-engineer HP bonus from stats of single unit; skills
    var Match = PatAliveDead.exec(AliveDead);
    ArmyArray[Number(ID.slice(1))] = Number(Match[1]) + Number(Fled);
    if (End && Attacker) SurvivorCarry += Number(Match[1]) * Units[ID]["carry"];
    // defenders fleeing return to base, so their loss is irrelevant
    var Num;
    var IsNotJunk = ! (ID in JunkList);
    TotalPop += Units[ID]["pop"] *
      (Number(Match[1]) + Number(Match[2]) + Number(Fled));
    LostPop += Units[ID]["pop"] *
      (Number(Match[2]) + Number(Fled) * Attacker * ! AttackerWin);

    LostPop2 += Units[ID]["pop"] * IsNotJunk * 
      (Number(Match[2]) + Number(Fled) * Attacker * ! AttackerWin);
    TotalLife += Number(Life);
    ResList.forEach(
      function (Option) {
        var ResName = Option[0];
        Num = Units[ID][ResName.toLowerCase()] *
          (Number(Match[2]) + Number(Fled) * Attacker * ! AttackerWin)
          || 0;
        eval("Lost" + ResName + " += Num;");
        eval("Lost" + ResName + "2 += IsNotJunk * Num;");
      }
    );
    LostTotal = LostGold + LostIron + LostWood + LostFood;
    LostTotal2 = LostGold2 + LostIron2 + LostWood2 + LostFood2;

    // show unit upgrades
    if (! End) {
      var SingleLife = Number(Life) / Number(Match[1]);
      if (SingleLife) {
        SingleLife = SingleLife -
          ((DefenderLevels[Skills[Translate("Defender")]] + SpecialDefender) / 100) *
            Units[ID]["life"];
        if (
          ! UKMode && (ID in InfantryList || ID in RaceSpecific) &&
          (SpecialInfantry || Skills[Translate("Warrior")])
        ) {
          SingleLife = SingleLife -
            ((WarriorLevels[Skills[Translate("Warrior")]] + SpecialInfantry) / 100) *
              Units[ID]["life"]
        }
        var LevelBonus = 0;
        var AttackerLevel;
        var DefenderLevel = Number(Skills["Level"]);
        if (! Attacker && Skills[Translate("Defender")] != "0") {
          AttackerLevel = Number(
            document.getElementById("pageBody").getAttribute("GM_AttackerLevel")
          );
          LevelBonus = Math.max(0, (AttackerLevel - DefenderLevel) * 5);
        }
        SingleLife = SingleLife - (LevelBonus / 100) * Units[ID]["life"];
        var BonusDef = StripDecimals(
          100 * (SingleLife / Units[ID]["life"] - 1) /
            (UnitUpgrades[ID] ? UnitUpgrades[ID][3] : 1),
          2
        );
      } else {
        var BonusDef = "N/A";
      }
      var SingleAttack, BaseAttack;
      // reverse-engineer attack bonus from skills and stats
      var SingleAttack = (ID in MysticList ? Number(Building) : Number(Infantry)) /
        Number(Match[1]);
      if (SingleAttack) {
        SingleAttack = SingleAttack -
          (ID in InfantryList || ID in RaceSpecific) * (
            (WarriorLevels[Skills[Translate("Warrior")]] + SpecialInfantry) / 100
          ) * Units[ID]["infantry"] -
          (ID in ArcherList) * (ArcherLevels[Skills[Translate("Archer")]] / 100) *
            Units[ID]["infantry"] -
          (ID in CavalryList) *
            ((CavalryLevels[Skills[Translate("Cavalryman")]] + SpecialCavalry) / 100) *
            Units[ID]["infantry"] -
          (ID in MysticList) * (MysticLevels[Skills[Translate("Mystic")]] / 100) *
            Units[ID]["building"];
        var BonusAttack = StripDecimals(
          100 * (SingleAttack /
            (ID in MysticList ? Units[ID]["building"] : Units[ID]["infantry"])
          - 1) / (UnitUpgrades[ID] ? UnitUpgrades[ID][1] : 1),
          2
        );
      } else {
        var BonusAttack = "N/A";
      }
      var OutString = AliveDead + " / " + Fled +
        "<br />" + Translate("Inf") + ": " + Infantry + "<br />" +
        Translate("Cav") + ": " + Cavalry +
        "<br />" + Translate("Arch") + ": " + Archer + "<br />" +
        Translate("Myst") + ": " + Wizard +
        "<br />" + Translate("Life") + ": " + Life + "<br />" +
        Translate("Atk+") + ": " + String(BonusAttack) +
        "<br />" + Translate("Def+") + ": " + String(BonusDef) +
        (LevelBonus ? " (+" + String(LevelBonus) + "%)" : "");
      Army[N].innerHTML =
        "<a href='javascript:void(0);' id='unitPicSmall-" +
        ID.slice(1) + "'>" + "<font size='1'>" + OutString + "</font></a>";
    }
  }
  Node.setAttribute("GM_Life" + Attacker + Phase1 + End, String(TotalLife));

  // copy buttons
  if (! End) {
    var InsertionPoint = document.evaluate(
      "preceding-sibling::h3[1]",
      Node, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
    ).singleNodeValue;
    ["Formation", "Army"].forEach(
      function (Option) {
        var CopyButton = document.createElement("input");
        CopyButton.type = "button";
        CopyButton.value = Translate("Copy " + Option);
        CopyButton.addEventListener("click", eval("Copy" + Option), true);
        Node.parentNode.
          insertBefore(CopyButton, InsertionPoint.nextSibling);
        CopyButton.parentNode.
          insertBefore(document.createElement("br"), CopyButton.nextSibling);
        if (Option == "Army")
          CopyButton.setAttribute("GM_Army", uneval(ArmyArray));
      }
    );
  }

  if (End) {
    var NewNode = document.createElement("div");
    NewNode.innerHTML = (Attacker ? AttackerText : DefenderText);
    NewNode.innerHTML +=
      "<br />" + Translate("Lost Resources") + ": " + String(LostGold) + "+" +
      String(LostIron) + "+" + String(LostWood) + "+" + String(LostFood) +
      " = " + String(LostTotal) +
      "<br />" + Translate("Non-Junk (Res)") + ": " + String(LostGold2) + "+" +
      String(LostIron2) + "+" + String(LostWood2) + "+" + String(LostFood2) +
      " = " + String(LostTotal2) + "<br />" + Translate("Total Pop") + ": " + TotalPop +
      "<br />" + Translate("Lost Pop") + ": " + String(LostPop) + " (" +
      (TotalPop
        ? StripDecimals(Round(100 * LostPop / TotalPop, 0.01), 2)
        : "0") +
      "%)" + "<br />" + Translate("Lost Life") + ": " +
      String(
        Number(document.evaluate(
          "//*[@GM_Life" + String(Attacker) + String(Phase1) + "false]",
          document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
        ).
          singleNodeValue.getAttribute(
            "GM_Life" + String(Attacker) + String(Phase1) + "false"
          )
        ) - TotalLife
      );
    NewNode.id = "GM_Losses" + String(Attacker) + String(Phase1);
    NewNode.setAttribute(
      "GM_Losses",
      uneval([LostGold, LostIron, LostWood, LostFood, LostTotal, LostPop])
    );
    NewNode.setAttribute(
      "GM_NonJunkLosses",
      uneval([LostGold2, LostIron2, LostWood2, LostFood2, LostTotal2])
    );
    ResultNode.appendChild(NewNode);
    var ScavengeNode = document.createElement("div");
    ScavengeNode.id = "GM_Scavenge" + String(Attacker) + String(Phase1);
    var EstScavenge = String(Math.ceil(LostTotal * 0.15));
    ScavengeNode.innerHTML =
      Translate("Estimated Scavenge") + ": " + EstScavenge + "<br /><br />";
    ScavengeNode.setAttribute("GM_Scavenge", EstScavenge);
    ResultNode.appendChild(ScavengeNode);
  }
  if (Attacker && End) {
    var DataNode = document.getElementById("GM_DataNode");
    if (! DataNode) {
      DataNode = document.createElement("div");
      DataNode.id = "GM_DataNode";
      document.body.appendChild(DataNode);
    }
    DataNode.setAttribute("SurvivorCarry", String(SurvivorCarry));
  }
}

var PageMatch = new RegExp(
  Host + "/battle-report\\.php\\?id=.+(?:&world_id=.+)?", "i"
).test(window.location);
if (PageMatch) {
  var AttackerText = document.getElementsByClassName("box round")[0].
    getElementsByTagName("h3")[0].innerHTML;
  var DefenderText = document.getElementsByClassName("box round")[0].
    getElementsByTagName("h3")[1].innerHTML;

  // link coords to map, not profile
  var Coords = document.evaluate(
    "//a[starts-with(@href, 'preview.php')]", document, null,
    XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  var PatCoords = /(\d+):(\d+)/i;
  for (var N = 0; N < Coords.snapshotLength; N++) {
    var Match = PatCoords.exec(Coords.snapshotItem(N).innerHTML);
    if (Match) {
      Coords.snapshotItem(N).href =
        "map.php?setx=" + Match[1] + "&sety=" + Match[2];
      var Name = GetCastleName(Match);
      Name[0].length ? Name = "--" + Name[0] : (Name[1].length ? Name = "--" + Name[1][1] : Name = "");
      Coords.snapshotItem(N).innerHTML += Name;
    }
  }
  var AttackerSkills = ParseSkills(document.getElementById(AttackerText + "Info"));
  document.getElementById("pageBody").
    setAttribute("GM_AttackerLevel", AttackerSkills["Level"]);
  var Multis = document.evaluate(
    "//*[@id='" + DefenderText + "Info']", document, null,
    XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  var DefenderSkills = ParseSkills(Multis.snapshotItem(0));
  document.getElementById("pageBody").
    setAttribute("GM_DefenderLevel", DefenderSkills["Level"]);

  if (Multis.snapshotLength > 1) {
    var SupporterSkills = ParseSkills(Multis.snapshotItem(1))
    document.getElementById("pageBody").
      setAttribute("GM_SupporterLevel", SupporterSkills["Level"]);
  } else {
    var SupporterSkills = null;
  }

  // phase 1--attacker vs. defender
  var Attacker = document.evaluate(
    "//div[@class='infosmall'][1]/" +
      "preceding-sibling::div/div[@class='armyOrder attacker']",
    document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  var Defender = document.evaluate(
    "//div[@class='infosmall'][1]/" +
      "preceding-sibling::div/div[@class='armyOrder defender']",
    document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  ParseUnits(Attacker.snapshotItem(0), AttackerSkills, true, false);
  ParseUnits(Defender.snapshotItem(0), DefenderSkills, true, false);
  ParseUnits(
    Attacker.snapshotItem(Attacker.snapshotLength - 1), AttackerSkills,
    true, true
  );
  ParseUnits(
    Defender.snapshotItem(Defender.snapshotLength - 1), DefenderSkills,
    true, true
  );
  // attacker vs. supporter
  if (SupporterSkills) {
    var Attacker2 = document.evaluate(
      "//div[@class='infosmall'][1]/" +
        "following::div[@class='armyOrder attacker']",
      document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
    );
    var Supporter = document.evaluate(
      "//div[@class='infosmall'][1]/" +
        "following::div[@class='armyOrder defender']",
      document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
    );
    ParseUnits(Attacker2.snapshotItem(0), AttackerSkills, false, false);
    ParseUnits(Supporter.snapshotItem(0), SupporterSkills, false, false);
    ParseUnits(
      Attacker2.snapshotItem(Attacker2.snapshotLength - 1),
      AttackerSkills, false, true
    );
    ParseUnits(
      Supporter.snapshotItem(Supporter.snapshotLength - 1),
      SupporterSkills, false, true
    );
  }

  var ResultNode = document.evaluate(
    "//div[@class='infosmall'][last()]",
    document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
  ).singleNodeValue;
  Attacker = Number(
    document.getElementById("GM_Scavengetruetrue").getAttribute("GM_Scavenge")
  );
  Defender = Number(
    document.getElementById("GM_Scavengefalsetrue").getAttribute("GM_Scavenge")
  );
  if (SupporterSkills) {
    Attacker += Number(
      document.getElementById("GM_Scavengetruefalse").
        getAttribute("GM_Scavenge")
    );
    Defender += Number(
      document.getElementById("GM_Scavengefalsefalse").
        getAttribute("GM_Scavenge")
    );
  }
  var AttackerLoss = eval(document.getElementById(
    "GM_Lossestruetrue"
  ).getAttribute("GM_Losses"));
  var AttackerNonJunk = eval(document.getElementById(
    "GM_Lossestruetrue"
  ).getAttribute("GM_NonJunkLosses"));
  var DefenderLoss = eval(document.getElementById(
    "GM_Lossesfalsetrue"
  ).getAttribute("GM_Losses"));
  var DefenderNonJunk = eval(document.getElementById(
    "GM_Lossesfalsetrue"
  ).getAttribute("GM_NonJunkLosses"));
  if (SupporterSkills) {
    var TempArr = eval(document.getElementById(
      "GM_Lossestruefalse"
    ).getAttribute("GM_Losses"));
    AttackerLoss = TwoArraySum(AttackerLoss, TempArr);
    var TempArr = eval(document.getElementById(
      "GM_Lossesfalsefalse"
    ).getAttribute("GM_Losses"));
    DefenderLoss = TwoArraySum(DefenderLoss, TempArr);
    var TempArr = eval(document.getElementById(
      "GM_Lossestruefalse"
    ).getAttribute("GM_NonJunkLosses"));
    AttackerNonJunk = TwoArraySum(AttackerNonJunk, TempArr);
    var TempArr = eval(document.getElementById(
      "GM_Lossesfalsefalse"
    ).getAttribute("GM_NonJunkLosses"));
    DefenderNonJunk = TwoArraySum(DefenderNonJunk, TempArr);
  }

  var Scavenge = Attacker + Defender;
  var NewNode = document.createElement("div");
  NewNode.innerHTML =
    (SupporterSkills
      ? Translate("Total Attacker Loss (Res/Pop)") + ": " +
          String(AttackerLoss[0]) + "+" + String(AttackerLoss[1]) + "+" +
          String(AttackerLoss[2]) + "+" + String(AttackerLoss[3]) + " = " +
          String(AttackerLoss[4]) + "; " + String(AttackerLoss[5]) + "<br />" +
          Translate("Non-Junk (Res)") + ": " +
          String(AttackerNonJunk[0]) + "+" + String(AttackerNonJunk[1]) + "+" +
          String(AttackerNonJunk[2]) + "+" + String(AttackerNonJunk[3]) + " = " +
          String(AttackerNonJunk[4]) + "<br />" +
          Translate("Total Defender Loss (Res/Pop)") + ": " +
          String(DefenderLoss[0]) + "+" + String(DefenderLoss[1]) + "+" +
          String(DefenderLoss[2]) + "+" + String(DefenderLoss[3]) + " = " +
          String(DefenderLoss[4]) + "; " + String(DefenderLoss[5]) + "<br />" +
          Translate("Non-Junk (Res)") + ": " +
          String(DefenderNonJunk[0]) + "+" + String(DefenderNonJunk[1]) + "+" +
          String(DefenderNonJunk[2]) + "+" + String(DefenderNonJunk[3]) + " = " +
          String(DefenderNonJunk[4]) + "<br />"
      : ""
    ) +
    Translate("Estimated Total Scavenge") + ": " + String(Attacker) + "+" +
    String(Defender) + " = " + String(Scavenge) + " (" +
    String(Math.ceil(Scavenge / Units["u10"]["carry"])) + " " +
    Translate("LC") + "/" +
    String(Math.ceil(Scavenge / Units["u19"]["carry"])) + " " +
    Translate("CA") + ")";
  ResultNode.appendChild(NewNode);
  var SurvivorCarry = Number(
    document.getElementById("GM_DataNode").getAttribute("SurvivorCarry")
  );
  var InsertionPoint = document.evaluate(
    "//ul[@class='battleResults']/descendant::span[@class='cleanupGold']/" +
      "ancestor::li",
    document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  var TotalScavenge = Stolen = 0, Current;
  for (var N = 0; N < InsertionPoint.snapshotLength; N++) {
    var Scavenge = InsertionPoint.snapshotItem(N).
      getElementsByClassName("cleanupPopulation").length > 0;
    var Actual =
      Number(document.getElementsByClassName("cleanupGold")[N].innerHTML) +
      Number(document.getElementsByClassName("cleanupIron")[N].innerHTML) +
      Number(document.getElementsByClassName("cleanupWood")[N].innerHTML) +
      Number(document.getElementsByClassName("cleanupFood")[N].innerHTML);
    NewNode = document.createElement("li");
    if (Scavenge) {
      TotalScavenge += Actual;
    } else {
      Stolen = Actual;
    }
    if (N == InsertionPoint.length - 1 && Scavenge) Actual = TotalScavenge;
    NewNode.innerHTML = String(Actual) +
      " (" + String(Math.ceil(Actual / Units["u10"]["carry"])) + " " +
      Translate("LC") + "/" +
      String(Math.ceil(Actual / Units["u19"]["carry"])) + " " +
      Translate("CA") + ")";
    InsertionPoint.snapshotItem(N).parentNode.
      insertBefore(NewNode, InsertionPoint.snapshotItem(N).nextSibling);
  }
  if (Stolen) {
    var NewNode = document.createElement("li");
    var Capacity = Number(
      document.getElementById("GM_DataNode").getAttribute("SurvivorCarry")
    );
    NewNode.innerHTML = Translate("Survivor Capacity") + ": " +
      String(Stolen) + "/" + String(Capacity) + " (" +
      StripDecimals(Round(100 * Stolen / Capacity, 0.01), 2) + "%)";
    InsertionPoint.snapshotItem(N - 1).parentNode.appendChild(NewNode);
  }
}

/******************************************************************************/
// sim tools

function MinMax(Event) {
  var MaxType = Event.target.getAttribute("maxtype");
  var FirstChar = Event.target.parentNode.getAttribute("id");
  FirstChar = FirstChar.slice(FirstChar.length - 1);
  switch (FirstChar) {
    case "1": FirstChar = "a"; break;
    case "2": FirstChar = "d"; break;
    case "3": FirstChar = "s"; break;
  }
  var SecondChar;
  var SkillPaste = false;
  switch (Event.target.getAttribute("GM_Type")) {
    case "Min Unit"       :
    case "Paste Unit"     :
    case "Add Unit"       : SecondChar = ""; break;
/*    case "Paste Clipboard":
    case "Add Clipboard"  : SecondChar = ""; break;*/
    case "Max Upgrade"    : SecondChar = "(?:a|d)"; break;
    case "Paste Skill"    : SkillPaste = true; SecondChar = "s"; break;
    case "Max Skill"      : SecondChar = "s"; break;
  }
  var Pat = new RegExp("^" + FirstChar + SecondChar + "_(\\d+)$", "i");
  var IDs = document.evaluate(
    "//*[@id]", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  if (MaxType == "2" && Event.target.innerHTML == "Paste Clipboard") {
    ParseClipboardUnits(unsafeWindow.GetClipboard());
  }
  var ArmyArray = eval(GM_getValue(SavePrefix + "_CopyArmy", "")) || "";
  var SavedSkills = eval(GM_getValue(SavePrefix + "_CopySkills", "")) || "";
  var Matches = 0;
  for (var N = 0; N < IDs.snapshotLength; N++) {
    var ID = Pat.exec(IDs.snapshotItem(N).getAttribute("id"));
    if (ID) {
      Matches += 1;
      if (MaxType == "0") {
        IDs.snapshotItem(N).value = "0";
      } else if (MaxType == "1") {
        // max varies
        if (SecondChar == "s") { // skill
          if (! SkillPaste) {
            IDs.snapshotItem(N).value = "3";
          } else {
            IDs.snapshotItem(N).value = SavedSkills[Matches] || "0";
          }
        } else { // attack/defense
          ID = "u" + ID[1];
          if (! (ID in Unupgraded)) {
            if (ID in MysticList) IDs.snapshotItem(N).value = "5";
            if (
              ! (ID in Unupgraded) &&
              (ID in InfantryList || ID in CavalryList || ID in ArcherList)
            ) {
              IDs.snapshotItem(N).value = "10";
            }
          }
        }
      } else if (MaxType == "2") { // paste
        if (Event.target.getAttribute("GM_Type") == "Add Unit") {
          IDs.snapshotItem(N).value = String(
            Number(IDs.snapshotItem(N).value) +
            (Number(ArmyArray[Number(ID[1])]) || 0)
          );
        } else {
          IDs.snapshotItem(N).value = ArmyArray[Number(ID[1])] || "0";
        }
      }
    }
  }
  if (SkillPaste) {
    document.evaluate(
      "//input[@name='" + FirstChar + "level']",
      document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
    ).singleNodeValue.value = SavedSkills[0] || "10";
  }
}
  
var PageMatch = new RegExp(Host + "/calculator\\.php", "i").
  test(window.location);
if (PageMatch) {
  var Header = document.evaluate(
    "//table/thead/tr",
    document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
  ).singleNodeValue.getElementsByTagName("td");
  for (var N = 1; N < Header.length; N++) {
    [
      ["Unit", "0", "Min Unit"],
      ["Paste", "2", "Paste Unit"],
      ["Add", "2", "Add Unit"],
/*      ["PasteClip", "2", "Paste Clipboard"], ["AddClip", "2", "Add Clipboard"],*/
      ["Upgrade", "1", "Max Upgrade"],
      ["PasteSkill", "1", "Paste Skill"],
      ["Skill", "1", "Max Skill"]
    ].forEach(
      function (Option) {
        var NewNode = document.createElement("div");
        NewNode.setAttribute("id", "GM_" + Option[0] + String(N));
        NewNode.innerHTML =
          "<a href='javascript:void(0);' maxtype='" + Option[1] +
          "' GM_Type='" + Option[2] + "'>" + Translate(Option[2]) + "</a><br />";
        NewNode.addEventListener("click", MinMax, true);
        Header[N].appendChild(NewNode);
      }
    );
  }
}

/******************************************************************************/
// large map rejigging

function SaveMapData(MapData) {
  MapData = eval(MapData);
  for (var X = 0; X < 8; X++) {
    for (var Y = 0; Y < 8; Y++) {
      window.setTimeout(
        function (MapData, X, Y) {
          GM_setValue(
            SavedHost + "_MapData_" + MapData[X][Y][0] + "_" +
              MapData[X][Y][1],
            uneval(MapData[X][Y])
          );
        },
        0,
        MapData, X, Y
      );
    }
  }
}

function TrapMapUpdate(Event) {
  var PatLeft = /left: (\d+)px/i;
  var PatTop = /top: (\d+)px/i;
  var Now = new Date();
  var ID = Event.relatedNode.getAttribute("id");
  if (ID == "now_map" || ID == "new_map" || ID == "old_map") {
    function DisplayPlayerMessage2(Event) {
      // no idea why this is necessary...
      var TempNode = Event.target;
      while (! TempNode.hasAttribute("GM_X") && TempNode.parentNode) {
        TempNode = TempNode.parentNode;
      }
      unsafeWindow.displayPlayerMessage(
        0, "", TempNode.getAttribute("GM_X"), TempNode.getAttribute("GM_Y")
      );
    }
    if (! UKMode) {
      var BaseX = unsafeWindow._xMap, BaseY = unsafeWindow._yMap;
    } else {
      var BaseX = unsafeWindow.x, BaseY = unsafeWindow.y;
    }
    var CurrX, CurrY, NewNode;
    var FocusCoords = /\?setx=(\d+)&sety=(\d+)$/i.exec(window.location);
    FocusCoords ?
      FocusCoords = [FocusCoords[1], FocusCoords[2]] : FocusCoords = [0, 0];
    var Map = Event.relatedNode;
    var Nodes = document.evaluate(
      "descendant-or-self::div[@id='" + ID + "']/descendant::div[contains(@class,'mapBlock')]",
      Map, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
    );
    var Present = [];
    for (var N = 0; N < Nodes.snapshotLength; N++) {
      if (Nodes.snapshotItem(N).hasAttribute("title")) {
        CurrX = Number(PatLeft.exec(Nodes.snapshotItem(N).getAttribute("style"))[1]) / 50;
        CurrY = Number(PatTop.exec(Nodes.snapshotItem(N).getAttribute("style"))[1]) / 50;
        Nodes.snapshotItem(N).setAttribute("GM_X", CurrX + BaseX);
        Nodes.snapshotItem(N).setAttribute("GM_Y", CurrY + BaseY);
        Present[[CurrX, CurrY].toSource()] = null;
      }
    }
    var InsertionPoint = Map.getElementsByClassName("wrapper")[0];
    if (! InsertionPoint) return 0;
    for (var X = 0; X < 8; X++) {
      for (var Y = 0; Y < 8; Y++) {
        if (! ([X, Y].toSource() in Present)) {
          NewNode = document.createElement("div");
          NewNode.setAttribute("class", "mapBlock");
          NewNode.setAttribute(
            "style",
            "left: " + String(X * 50) + "px; top: " + String(Y * 50) + "px;"
          );
          NewNode.setAttribute("GM_X", X + BaseX);
          NewNode.setAttribute("GM_Y", Y + BaseY);
          InsertionPoint.insertBefore(NewNode, InsertionPoint.lastChild);
        }
      }
    }
    var PatGold = /res_gold.gif.+?>\s*(\d+)\s*/i;
    var PatIron = /res_iron.gif.+?>\s*(\d+)\s*/i;
    var PatWood = /res_wood.gif.+?>\s*(\d+)\s*/i;
    var PatFood = /res_food.gif.+?>\s*(\d+)\s*/i;
    var PatPop = /res_pop.gif.+?>\s*(\d+)\s*/i;
    var PatCoords = /header=\[.+?\((\d+):(\d+)\)\]/i;
    var PatCastleCoords = /^\s*header=\[\s*.*?([^>]*?)\s*\((\d+):(\d+)\)/i;
    var PatCapital = new RegExp(
      "img" + (UKMode ? "_ut" : "") + "/revolution/icon_crown\\.gif", "i"
    );
    var PatPlayer = new RegExp(
      "img" + (UKMode ? "_ut" : "") + "/revolution/overview/player\\.gif' /> " +
        "(.+?)\\s*<",
      "i"
    );
    var PatLevel = /exp\.gif.+?>\s*\d+\s*\((\d+)\)\s*<br\/>/i;
    var PatClan = new RegExp(
      "img" + (UKMode ? "_ut" : "") + "/revolution/overview/clan\\.gif'\/> " +
        "(.+?)\s*<",
      "i"
    );
    var PatLogo = /<img src='clanLogos.+?\/>/i;
    var RelativeX, RelativeY;
    // if square has nodes, calculate relative/global coords
    // init empty grid
    var MapData = [], GlobalX1, GlobalY1;
    for (var X = 0; X < 8; X++) {
      MapData[X] = new Array(8);
      for (var Y = 0; Y < 8; Y++) {
        MapData[X][Y] = [String(BaseX + X), String(BaseY + Y)];
      }
    }
    if (Nodes.snapshotLength > 0) { // grid has cities
      for (var N = 0; N < Nodes.snapshotLength; N++) {
        var TempText = Nodes.snapshotItem(N).getAttribute("title");
        if (TempText) {
          var Player = PatPlayer.exec(TempText);
          TempText = TempText.replace(PatLogo, "");
          var Coords = PatCoords.exec(TempText);
          Coords = [Coords[1], Coords[2]];
          RelativeX =
            Number(Nodes.snapshotItem(N).getAttribute("GM_X")) - BaseX;
          RelativeY =
            Number(Nodes.snapshotItem(N).getAttribute("GM_Y")) - BaseY;
          var Gold = PatGold.exec(TempText)[1];
          var Iron = PatIron.exec(TempText)[1];
          var Wood = PatWood.exec(TempText)[1];
          var Food = PatFood.exec(TempText)[1];
          var Scavenge =
            Number(Gold) + Number(Iron) + Number(Wood) + Number(Food);
          var Pop = PatPop.exec(TempText);
          Pop ? Pop = Number(Pop[1]) : Pop = 0;
          if (PatDate.test(TempText)) {
            var LastActivityMS = LocalTime(TempText, TimeOffset);
            var LastActivity = DateFormat(LastActivityMS);
            TempText = TempText.replace(PatDate, LastActivity);
          } else {
            var LastActivityMS = null;
            var LastActivity = null;
          }
          var Colour = "white";
          if (Scavenge > 0 || Pop >= HealThreshold) {
            if (Pop >= HealThreshold || Scavenge >= ScavengeThreshold) {
              Colour = "lightgreen";
            } else {
              if (Scavenge > 0) Colour = "blue";
            }
            var Labelled = document.evaluate(
              "descendant::*[@GM_JunkAttr]",
              Nodes.snapshotItem(N), null, XPathResult.FIRST_ORDERED_NODE_TYPE,
              null
            ).singleNodeValue;
            if (! Labelled) {
              Nodes.snapshotItem(N).innerHTML +=
                "<span GM_JunkAttr='1' style='color: " + Colour + "; font-family: sans-serif;'><b>" +
                String(Math.ceil(Scavenge / Units["u10"]["carry"])) + " " + Translate("LC") + "<br /                    : StripDecimals(
                        Round((Now - CurrSquare[N]) / 86400000, 0.01), 2
                      )
                  ) + "</td><td>" +
                  (CurrSquare[N] == null ? "?" : String(CurrSquare[N])) +
                  "</td>"
                : CurrSquare[N]
              ) +
              "</td>";
          }
          NewText += "</tr>";
        }
      }
    }
  }
  NewText += "</tbody></table></center><br /><br />";
  NewTab.innerHTML = NewText;
}

function UpdateMapDists(Event) {
  var NewNode = document.getElementById("GM_CastleDists");
  if (! NewNode) {
    var InsertionPoint = document.getElementsByClassName("largeMapFrame")[0];
    NewNode = document.createElement("div");
    NewNode.id = "GM_CastleDists";
    NewNode.setAttribute("style", "text-align: center");
    InsertionPoint.parentNode.insertBefore(NewNode, InsertionPoint.nextSibling);
    var QuickNode = document.createElement("div");
    QuickNode.setAttribute("style", "text-align: center;");
    QuickNode.innerHTML =
      (! UKMode
         ? "<label>" + Translate("Unit 27") + ": </label><input " +
           "name='units[27]' type='text' size='5' value='0' id='units[27]' /> "
         : ""
      ) +
      "<label>" + Translate("Quick Roads") + ": </label><input id='" +
      "GM_QuickRoads' type='checkbox' />";
    InsertionPoint.parentNode.
      insertBefore(QuickNode, InsertionPoint.nextSibling);
    if (! UKMode)
      document.getElementById("units[27]").
        addEventListener("change", UpdateMapDists, true);
    document.getElementById("GM_QuickRoads").
      addEventListener("change", UpdateMapDists, true);
    var SpeedNode = document.createElement("div");
    SpeedNode.setAttribute("style", "text-align: center;");
    var Temp = " <label>" + Translate("Speed") +
      ": </label><select id='GM_SelectSpeed'>";
    for (var M = Speeds.length - 1; M >= 0; M--) {
      Temp += "<option>" + Speeds[M][0] + "</option>";
    }
    SpeedNode.innerHTML = Temp + "</select>";
    QuickNode.parentNode.insertBefore(SpeedNode, QuickNode.nextSibling);
    document.getElementById("GM_SelectSpeed").
      addEventListener("change", UpdateMapDists, true);
    NewNode.innerHTML = "";
  }
  var Target;
  if (! Event) {
    var Match = /setx=(\d+)&sety=(\d+)/i.exec(window.location);
    Match ? Target = [Match[1], Match[2]] : Target = null;
    if (! Target) {
      try {
        Target = eval(
          document.getElementById("GM_QuickRoads").getAttribute("GM_Target")
        );
      } catch(Error) {
        Target = null;
      }
    }
  } else if (
    Event.target && (
      Event.target.id == "GM_QuickRoads" || Event.target.id == "GM_SelectSpeed"
    )
  ) {
    Target = eval(
      document.getElementById("GM_QuickRoads").getAttribute("GM_Target")
    );
  } else {
    try {
      // no idea why this is necessary...
      var TempNode = Event.target;
      while (! TempNode.hasAttribute("GM_X") && TempNode.parentNode) {
        TempNode = TempNode.parentNode;
      }
      Target =
        [TempNode.getAttribute("GM_X"), TempNode.getAttribute("GM_Y")];
      document.getElementById("GM_QuickRoads").
        setAttribute("GM_Target", uneval(Target))
    } catch(Error) {
      Target = null;
    }
  }
  if (Target) {
    document.getElementById("GM_QuickRoads").
      setAttribute("GM_Target", uneval(Target));
    var Name = GetCastleName([null, Target[0], Target[1]]);
    var SelSpeed = document.getElementById("GM_SelectSpeed");
    SelSpeed = Number(SelSpeed.options[SelSpeed.selectedIndex].innerHTML);
    NewNode.innerHTML = "<center><table><tr><td>@" +
      Target[0] + ":" + Target[1] +
      (Name[0].length
         ? " (" + Name[0] + ")"
         : (Name[1].length ? " (" + Name[1].join(", ") + ")" : "")
      ) +
      "<br />" + CalcCastleDists(Target, SelSpeed, true, true)[0] +
      "</td></tr></table></center>";
  }
}

// update map tooltip for quick roads checkbox
function LookForoDv(Event) {
  var LastDiv = document.evaluate(
    "./div[last()]",
    document.body, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
  );
  if (LastDiv) {
    LastDiv = LastDiv.singleNodeValue;
    if (LastDiv.getAttribute("style").indexOf("z-index: 100;") > -1) {
      LastDiv = LastDiv.getElementsByTagName("div");
      LastDiv = LastDiv[LastDiv.length - 2];
      if (LastDiv.innerHTML) {
        var TempNode = document.createElement("div");
        TempNode.innerHTML = LastDiv.innerHTML;
        var DelNode = document.evaluate(
          "descendant::span[@junkattr='1']",
          TempNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
        ).singleNodeValue;
        if (DelNode) TempNode.removeChild(DelNode);
        var PatCoords = /.+?\((\d+):(\d+)\)\s*$/i;
        var Coords = PatCoords.exec(TempNode.innerHTML).slice(1);
        var Distance = Dist(Coords);
        window.removeEventListener("DOMNodeInserted", LookForoDv, true);
        LastDiv.innerHTML = "<span style='font-size: 80%' junkattr='1'>" +
          Translate("Merchant") + " " +
          MarchTime(Distance, MerchantLevels[MerchantLevel], "<br>", [0], false, false) +
          Translate("Fastest") + " " +
          MarchTime(Distance, Speeds[Speeds.length - 1][0], "<br>", [0], false, false) +
          Translate("Slowest") + " " + MarchTime(Distance, Speeds[0][0], "<br>", [0], false, false) +
          "</span>" + TempNode.innerHTML;
        window.addEventListener("DOMNodeInserted", LookForoDv, true);
      }
    }
  }
}

// filter out non-march options for pop-up, for non-city squares
function CustomMapMessage(Event) {
  var Show =
    (document.getElementById("playerMessageProfile").href.slice(-2) != "=0");
  var Message = document.getElementById("playerMessage");
  var Links = document.evaluate(
    "descendant::a" +
      "[not(@class='closeMessage') and not(@id='playerMessageMarch')]",
    Message, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  for (let N = 0; N < Links.snapshotLength; N++) {
    Links.snapshotItem(N).
      setAttribute("style", "display: " + (Show ? "block" : "none") + ";");
  }
}

var PageMatch = new RegExp(Host + "/map\\.php", "i").test(window.location);
if (PageMatch) {
  document.getElementById("playerMessage").
    addEventListener("DOMAttrModified", CustomMapMessage, true);
  window.addEventListener("DOMNodeInserted", LookForoDv, true);
  window.addEventListener("keypress", PasteCoords, true);
  document.getElementsByClassName("gotoButton")[0].
    addEventListener("click", MapZoom, true);
  document.getElementById("searchX").form.
    addEventListener("keypress", HandleEnter, true);
  document.getElementById("searchY").form.
    addEventListener("keypress", HandleEnter, true);
  document.addEventListener("DOMNodeInserted", TrapMapUpdate, true);
  var InsertionPoint = document.getElementsByClassName("largeMapFrame")[0];
  var Button = document.createElement("center");
  Button.innerHTML = "<input type='button' value='" +
    Translate("Generate Map Report") + "' />";
  Button.addEventListener("click", ReportMapData, true);
  InsertionPoint.parentNode.insertBefore(Button, InsertionPoint.nextSibling);
  UpdateMapDists();
  window.setInterval(UpdateMapDists, 1000);
}

/******************************************************************************/
// barracks resource use

function UpdateResUse(Event) {
  if (! document.getElementById("GM_TrainRes")) {
    var NewNode = document.createElement("div");
    NewNode.style.color = "white";
    NewNode.innerHTML = Translate("Total Resources to Train") + ": <span id='GM_TrainRes'>" +
      "</span><br />" + Translate("Total Time to Train") + ": <span id='GM_TrainTime'></span>";
    var InsertionPoint = document.getElementById("submit").parentNode;
    InsertionPoint.parentNode.insertBefore(NewNode, InsertionPoint);
  }
  var UsedRes = UsedTime = 0;
  var UnitsList = document.getElementsByClassName("unitItem activeUnit");
  var ToTrain, TrainRes, TrainSpeed, TrainTime;
  var IndividualRes = [0, 0, 0, 0];
  for (var N = 0; N < UnitsList.length; N++) {
    ToTrain = document.evaluate(
      "descendant::input[@type='text']", UnitsList[N], null,
      XPathResult.FIRST_ORDERED_NODE_TYPE, null
    ).singleNodeValue;
    TrainSpeed = document.evaluate(
      "following-sibling::select", ToTrain, null,
      XPathResult.FIRST_ORDERED_NODE_TYPE, null
    ).singleNodeValue;
    if (TrainSpeed) {
      TrainSpeed = TrainSpeed.selectedIndex;
      switch (TrainSpeed) {
        case (0): TrainSpeed = 1; break;
        case (1): TrainSpeed = 3; break;
        case (2): TrainSpeed = 5; break;
      }
    } else {
      TrainSpeed = 1;
    }
    TrainTime =
      ToSeconds(UnitsList[N].getElementsByClassName("unitTime")[0].innerHTML) /
        (Math.ceil(TrainSpeed / 2));
    ToTrain = Number(ToTrain.value);
    var ResNodes = document.evaluate(
      "descendant::li[starts-with(@class, 'needed')]", UnitsList[N], null,
      XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
    );
    TrainRes = 0;
    for (var M = 0; M < ResNodes.snapshotLength; M++) {
      var TempNum = ToTrain * TrainSpeed *
        Number(ResNodes.snapshotItem(M).innerHTML);
      IndividualRes[M] += TempNum;
      TrainRes += TempNum;
    }
    UsedRes += TrainRes;
    UsedTime += ToTrain * TrainTime;
  }
  document.getElementById("GM_TrainRes").innerHTML = String(UsedRes) + " (" +
    IndividualRes.join("/") + ")";
  document.getElementById("GM_TrainTime").innerHTML = HMS(Math.round(UsedTime));
}

var PageMatch = new RegExp(Host + "/barracks\\.php", "i").test(window.location);
if (PageMatch) {
  var Head = document.getElementsByTagName("head")[0];
  var NewScript = document.createElement("script");
  NewScript.innerHTML = UpdateResUse.toString();
  Head.appendChild(NewScript);

  var CancelGold = CancelIron = CancelWood = CancelFood = 0;
  function setMaximum(Form, Value) {
    document.forms.namedItem("build_army").elements.namedItem(Form).value =
      Value;
    UpdateResUse();
  }
  var NewScript = document.createElement("script");
  NewScript.innerHTML = setMaximum.toString();
  Head.appendChild(NewScript);
  var NewScript = document.createElement("script");
  NewScript.innerHTML = ToSeconds.toString();
  Head.appendChild(NewScript);
  var NewScript = document.createElement("script");
  NewScript.innerHTML = HMS.toString();
  Head.appendChild(NewScript);
  var NewScript = document.createElement("script");
  NewScript.innerHTML = Zero.toString();
  Head.appendChild(NewScript);

  var UnitTimes = new Array(), ID;
  var UnitsList = document.getElementsByClassName("unitItem activeUnit");
  for (var N = 0; N < UnitsList.length; N++) {
    var FormNode = document.evaluate(
      "descendant::input[@type='text']", UnitsList[N], null,
      XPathResult.FIRST_ORDERED_NODE_TYPE, null
    ).singleNodeValue;
    ID = "u" + /unit\[(\d+)\]/i.exec(FormNode.name)[1];
    FormNode.addEventListener("change", UpdateResUse, true);
    var Time = ToSeconds(document.evaluate(
      "descendant::span[@class='unitTime']", UnitsList[N], null,
      XPathResult.FIRST_ORDERED_NODE_TYPE, null
    ).singleNodeValue.innerHTML);
    UnitTimes[ID] = Time;
  }
  var PulldownList = document.evaluate(
    "//select[starts-with(@name,'speed')]", document, null,
    XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  for (var N = 0; N < PulldownList.snapshotLength; N++) {
    PulldownList.snapshotItem(N).setAttribute(
      "onchange",
      PulldownList.snapshotItem(N).getAttribute("onchange") + "UpdateResUse();"
    );
  }
  var InProgress = document.evaluate(
    "//div[@class='queueArmy']", document, null,
    XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  var PatID = new RegExp(
    "<img src=\".*?img" + (UKMode ? "_ut" : "") + "/units/thumbs/(\\d+)\\.jpg",
    "i"
  );
  var PatTimePerUnit = /<\/strong>[\s\n]*([\d:]*)\s*/i;
  var PatTraining = /<strong>(\d+) x /i;
  for (var N = 0; N < InProgress.snapshotLength; N++) {
    var ID = "u" + PatID.exec(InProgress.snapshotItem(N).innerHTML)[1];
    var Time = ToSeconds(
      PatTimePerUnit.exec(InProgress.snapshotItem(N).innerHTML)[1]
    )
    var Training =
      Number(PatTraining.exec(InProgress.snapshotItem(N).innerHTML)[1]);
    var TrainSpeed = [
      [Math.abs(Time - UnitTimes[ID]), 1],
      [Math.abs(2 * Time - UnitTimes[ID]), 3],
      [Math.abs(3 * Time - UnitTimes[ID]), 5]
    ];
    TrainSpeed.sort(function (a, b) {return CompNum(a[0], b[0]);});
    TrainSpeed = TrainSpeed[0][1];
    var CurrGold, CurrIron, CurrWood, CurrFood;
    ResList.forEach(
      function (Option) {
        eval(
          "Curr" + Option[0] + " = Math.floor(" +
            "Training * CancelUnit * Units[ID]['" +
            Option[0].toLowerCase() + "']);"
        );
        eval(
          "Cancel" + Option[0] + " += Curr" + Option[0] + ";"
        );
      }
    );
    var NewNode = document.createElement("div");
    NewNode.innerHTML = Translate("Cancel") + ": " + String(CurrGold) + "/" + String(CurrIron) +
      "/" + String(CurrWood) + "/" + String(CurrFood);
    NewNode.style.color = "white";
    InProgress.snapshotItem(N).appendChild(NewNode);
  }
  var InsertionPoint = document.getElementById("submit").parentNode;
  var NewNode = document.createElement("div");
  NewNode.style.color = "white";
  NewNode.innerHTML = Translate("Total Cancel") + ": " + String(CancelGold) + "/" +
    String(CancelIron) + "/" + String(CancelWood) + "/" + String(CancelFood);
  InsertionPoint.parentNode.insertBefore(NewNode, InsertionPoint);
  UpdateResUse();
}

/******************************************************************************/
// overview--capture merchants; check res needed for buildings

var PageMatch = new RegExp(Host + "/overview\\.php", "i").test(window.location);
if (PageMatch) {
  var CurrCastle = /<big>.+?\((\d+):(\d+)\)<\/big>/i.exec(
    document.getElementsByClassName("currentCastle")[0].innerHTML
  ).slice(1);
  var Limit = document.evaluate(
    "//div[@class='textformbox']/descendant::div[@class='right'][2]/" +
      "text()",
    document, null, XPathResult.STRING_TYPE, null
  ).stringValue;
  var Found = false;
  for (var N = 0; N < PlayerMerchants.length; N++) {
    if (
      PlayerMerchants[N][0] == CurrCastle[0] &&
      PlayerMerchants[N][1] == CurrCastle[1]
    ) {
      PlayerMerchants[N][2] = Limit; Found = true; break;
    }
  }
  if (! Found) {
    PlayerMerchants.push([CurrCastle[0], CurrCastle[1], Limit]);
  }
  GM_setValue(SavePrefix + "Merchants", uneval(PlayerMerchants));
  MerchantCapacity();

  // building notes
  var InsertionPoint = document.getElementById("overview");
  var ULNode = document.createElement("ul");
  ULNode.style.color = "white"; ULNode.style.marginLeft = "15px";
  InsertionPoint.insertBefore(ULNode, InsertionPoint.firstChild);
  var PatLevel = /.+?(\d+).*\]\s*body\[\]/i;
  for (var N = 1; N <= BuildingMaxID; N++) {
    var Level = 0;
    var Link = document.evaluate(
      "//a[contains(@id, 'b" + String(N) + "')]",
      document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
    ).singleNodeValue;
    if (Link) Level = Number(PatLevel.exec(Link.getAttribute("title"))[1]);
    var LINode = document.createElement("li");
    LINode.innerHTML = Buildings[N][0] + ": ";
    if (Level == Buildings[N].length - 1) {
      LINode.innerHTML += "--";
    } else {
      var ResNeeded = Buildings[N][Level + 1].slice(0, 4);
      LINode.innerHTML += "#" + String(Level + 1) + "; " +
        String(ArraySum(ResNeeded)) + "; ";
      ResNeeded = [
        ResNeeded[0] - GlobalGold, ResNeeded[1] - GlobalIron,
        ResNeeded[2] - GlobalWood, ResNeeded[3] - GlobalFood
      ];
      var WorldSpeed =
        Number(GM_getValue(SavePrefix + "WorldSpeed", 1));
      for (var M = 0; M < ResNeeded.length; M++) {
        var Colour;
        if (ResNeeded[M] <= 0)
          Colour = "lightgreen";
        else
          Colour = "red";
        ResNeeded[M] = "<span style='color: " + Colour + "'>" +
          String(ResNeeded[M]) + "</span>";
      }
      var TotalNeeded = ArraySum(ResNeeded);
      ResNeeded = ResNeeded.join("/");
      var BuildTime = HMS(ToSeconds(Buildings[N][Level + 1][4]) / WorldSpeed);
      LINode.innerHTML += ResNeeded + "; " + BuildTime;
    }
    ULNode.appendChild(LINode);
  }
}

/******************************************************************************/
// formation helpers

function PasteFormation(Event) {
  var Formation = GM_getValue(SavePrefix + "_CopyFormation", "");
  if (Formation) {
    Formation = eval(Formation);
    var PatXY = /(\d)_(\d)/i;
    var PatOldXY = /x\dy\d/i;
    var Node, OldClass, OldID, OldPos, Exists, XY;
    if (! UKMode) {
      var Nodes = document.evaluate(
        "//*[starts-with(@id,'div_units_')]",
        document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
      );
      for (var N = 0; N < Nodes.snapshotLength; N++) {
        Nodes.snapshotItem(N).setAttribute(
          "style",
          Nodes.snapshotItem(N).getAttribute("style").
            replace(/(top|left):\s*\d+(px)?/ig, "")
        );
      }
    }

    for (var N = 1; N < Formation.length; N++) {
      Position = Formation[N];
      if (Position) {
        // check for race-specific
        Node = document.getElementById("upos_" + String(N));
        var N2 = N;
        if (! Node) {
          for (var Special in RaceSpecific) {
            Node = document.evaluate(
              "//input[@id='upos_" + Special.slice(1) + "']",
              document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
            ).singleNodeValue;
            if (Node) {
              N2 = Number(Node.id.slice(5));
              break;
            }
          }
        }

        // need to detect conflicts
        Exists = document.evaluate(
          "//input[starts-with(@name, 'unitsPos[')][@value='" + Position + "']",
          document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
        ).singleNodeValue;
        if (Exists) {
          OldID = /upos_(\d+)/i.exec(Exists.id)[1];
          OldPos = document.getElementById("upos_" + String(N2)).value;
          document.getElementById("upos_" + OldID).value = OldPos;
          var TempNode = document.getElementById("div_units_" + OldID);
          OldClass = PatOldXY.exec(TempNode.getAttribute("class"))[0];
          RemoveClass(TempNode, OldClass);
          OldPos = PatXY.exec(OldPos);
          OldPos = "x" + OldPos[1] + "y" + OldPos[2];
          AddClass(TempNode, OldPos);
        }
        XY = PatXY.exec(Position);
        XY = "x" + XY[1] + "y" + XY[2];
        Node.value = Position;
        Node = document.getElementById("div_units_" + String(N2));
        OldClass = PatOldXY.exec(Node.getAttribute("class"))[0];
        RemoveClass(Node, OldClass);
        AddClass(Node, XY);
      }
    }
  }
}

var PageMatch = new RegExp(Host + "/army_order\\.php", "i").
  test(window.location);
if (PageMatch) {
  var DefaultText = "";
  if (! UKMode) {
    var DefaultText = document.evaluate(
      "//a[starts-with(@href,'army_order_save.php?delete=')]/ancestor::tbody/" +
        "descendant::b",
      document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
    );
    var FormationIDs = [];
    for (N = 0; N < DefaultText.snapshotLength; N++) {
      FormationIDs.push(
        /(\d+)\s*$/i.exec(document.evaluate(
          "preceding-sibling::a", DefaultText.snapshotItem(N), null,
          XPathResult.FIRST_ORDERED_NODE_TYPE, null
        ).singleNodeValue.href)[1]
      );
    }
    if (DefaultText.snapshotLength)
      DefaultText = DefaultText.snapshotItem(0).innerHTML;
  }

  // save formations list
  var Options = document.getElementById("army_orders");
  if (Options) {
    Options = Options.options;
    var Formations = [], CurrVal;
    for (var N = 0; N < Options.length; N++) {
      CurrVal = Options[N].value;
      Formations.push([
        UKMode
          ? Options[N].innerHTML
          : (ArrMember(CurrVal, FormationIDs) > -1
              ? Options[N].innerHTML.replace(new RegExp("\\s*\\(" + DefaultText + "\\)$", "i"), "")
              : Options[N].innerHTML),
        CurrVal
      ]);
    }
    GM_setValue(SavePrefix + "Formations", uneval(Formations));
  }

  var EnemyBox = document.getElementById("enemybox");
  // enable pasting of copied formation
  if (EnemyBox) {
    EnemyBox.innerHTML += "<br /><br /><center>" +
      "<input type='button' value='" + Translate("Paste Formation") +
      "' id='GM_PasteFormation' /></center>";
    document.getElementById("GM_PasteFormation").
      addEventListener("click", PasteFormation, true);
  }
}

/******************************************************************************/
// automatically grab relevant hero skills on page visit

var PageMatch = new RegExp(Host + "/skills\\.php", "i").test(window.location);
if (PageMatch) {
  var Skills = document.evaluate(
    "//a[starts-with(@href, 'info.php')]",
    document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  var RelevantSkills =
    {3: "Archer", 6: "Scout", 7: "Merchant", 9: "Mystic"};
  for (var N = 0; N < Skills.snapshotLength; N++) {
    if (N in RelevantSkills) {
      GM_setValue(
        SavePrefix + RelevantSkills[N],
        Skills.snapshotItem(N).previousSibling.previousSibling.innerHTML
      );
    }
  }
}

/******************************************************************************/
// automatically grab relevant upgrades on page visit

var PageMatch = new RegExp(Host + "/upgrades\\.php", "i").test(window.location);
if (PageMatch) {
  var InProgressInfo = document.evaluate(
    "//div[@class='queueBuilding ']/descendant::img",
    document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  var InProgress = [];
  for (var N = 0; N < UnitMaxID + 1; N++) {
    InProgress.push(0);
  }
  for (var N = 0; N < InProgressInfo.snapshotLength; N++) {
    var ProgressID = Number(new RegExp(
      "img" + (UKMode ? "_ut" : "") + "/units/thumbs/(\\d+)\\.jpg",
      "i"
    ).exec(InProgressInfo.snapshotItem(N).getAttribute("src"))[1]);
    InProgress[ProgressID] += 1;
  }
  var DoneUpgrades = document.evaluate(
    "//div[@class='upgradeItem']",
    document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
  );
  for (var N = 0; N < DoneUpgrades.snapshotLength; N++) {
    var DoneID = Number(
      /unitPic-(\d+)/i.exec(
        DoneUpgrades.snapshotItem(N).getElementsByClassName("unitPreview")[0].id
      )[1]
    );
    // in progress upgrades not labelled, so subtract all in progress levels
    // from observed attack level
    if (Units["u" + String(DoneID)]["wallcrasher"]) {
      var DoneLevel = String(Math.max(
        0,
        Number(
          DoneUpgrades.snapshotItem(N).
            getElementsByClassName("typeLevel")[0].innerHTML -
          InProgress[DoneID]
        )
      ));
      GM_setValue(
        SavePrefix + "u" + String(DoneID) + "a",
        String(DoneLevel)
      );
    }
  }
}

/******************************************************************************/
// user config

function ResetPrefs() {
  if (document.getElementById("GM_ResetCheck").checked) {
    var List = GM_listValues();
    var Pat = new RegExp(
      "^" + /[^.]+?\.(.+)$/i.exec(window.location.hostname)[1] + "_" +
        document.getElementById("GM_ResetWorld").value + "_",
      "i"
    );
    List.forEach(
      function (Option) {
        if (Pat.test(Option)) GM_deleteValue(Option);
      }
    );
  }
}

var ConfigNode = document.createElement("div");
ConfigNode.innerHTML = "<ul id='GM_Config' style='list-style: disc outside;" +
  " color: white; display: none; -moz-column-count: 3; margin-left: 100px;" +
  "'></ul>";
document.body.insertBefore(ConfigNode, document.body.firstChild);

var ConfigButton = document.createElement("input");
ConfigButton.type = "button";
ConfigButton.setAttribute("style", "float: left;");
ConfigButton.value = Translate("Options");
ConfigButton.addEventListener(
  "click",
  function () {
    var Node = document.getElementById("GM_Config");
    if (Node.style.display == "none") {
      Node.style.display = "block";
    } else {
      Node.style.display = "none";
    }
  },
  true
);
ConfigNode.insertBefore(ConfigButton, ConfigNode.firstChild);

var Configs = [
  ["Archer", Translate("Archer")], ["Scout", Translate("Scout")],
  ["Merchant", Translate("Merchant")], ["Mystic", Translate("Mystic")],
  ["MinHeal", Translate("Minimum Heal")],
  ["MinScavenge", Translate("Minimum Scavenge")],
  ["StealFrac", Translate("Steal Fraction")],
  ["WorldSpeed", Translate("World Speed"), "1"],
  Translate("ClanID"),
  [
    "ServerOffset",
    Translate("Server Offset, Minutes") + " (" +
      StripDecimals(Round(InitOffset, 0.01), 2) + "/" +
      StripDecimals(Round(1440 + InitOffset, 0.01), 2) + ")",
    "0"
  ],
  ["MaxX", Translate("Maximum X"), "200"],
  ["MaxY", Translate("Maximum Y"), "200"],
  ["DefaultAttack", Translate("Default Attack Formation"), ""]
];
for (var N in Units) {
  if (Units[N]["wallcrasher"]) {
    Configs.push(
      [N + "a", Units[N]["name"] + " " + Translate("Atk+")]
    );
  }
}

var InsertionPoint = document.getElementById("GM_Config");
Configs.forEach(
  function(Config) {
    var Name = Config;
    var Label = Config;
    if (typeof(Name) != "string") {
      Name = Config[0]; Label = Config[1];
    }
    var Default = GM_getValue(SavePrefix + Name, "0");
    if (typeof(Config) != "string" && Config.length > 2 && Default == "0")
      Default = Config[2];
    var NewNode = document.createElement("li");
    NewNode.innerHTML = "<input type='text' value='" + Default +
      "' id='GM_Config" + Name + "' size='10' /> " + Label;
    InsertionPoint.appendChild(NewNode);
  }
);
var SaveButton = document.createElement("input");
SaveButton.type = "button";
SaveButton.value = Translate("Save");
SaveButton.addEventListener(
  "click",
  function () {
    Configs.forEach(
      function(Config) {
        var Name = Config;
        if (typeof(Name) != "string") {
          Name = Config[0];
        }
        GM_setValue(
          SavePrefix + Name,
          document.getElementById("GM_Config" + Name).value
        );
      }
    );
  },
  true
);
InsertionPoint.appendChild(document.createElement("br"));
InsertionPoint.appendChild(SaveButton);
InsertionPoint.appendChild(document.createElement("br"));
InsertionPoint.appendChild(document.createElement("br"));

var ClearStuff = document.createElement("div");
ClearStuff.innerHTML =
  "<input id='GM_Reset' type='button' value='" +
  Translate("Delete ALL Data (Map Too)") + "' />" +
  "<br />" + Translate("For world") + ": " +
  "<input id='GM_ResetWorld' type='text' size='10' value='0' />" +
  "<br />" + Translate("Really! (This could take a long time.)") + " " +
  "<input id='GM_ResetCheck' type='checkbox' value='false' />";
InsertionPoint.appendChild(ClearStuff);

document.getElementById("GM_Reset").addEventListener("click", ResetPrefs, true);

/******************************************************************************/
// logout: cleanup

function CleanOptions(Event) {
  Event.stopPropagation();
  Event.preventDefault();
  var List = GM_listValues();
  var Pat = /^QuickGo\d+((Formation)|X|Y|(FormationNum))?$/i;
  List.forEach(
    function (Item) {if (Pat.test(Item)) GM_deleteValue(Item);}
  );
  window.location.href = Host + "/logout.php";
}

var Logout = document.evaluate(
  "//a[contains(@href, 'logout.php')]",
  document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
).singleNodeValue;
Logout.addEventListener("click", CleanOptions, true);

/******************************************************************************/

Node = document.getElementsByClassName("coinsTip");
if (Node) unsafeWindow.closeInfoTooltip();
Node = document.getElementById("advisorMessage");
if (Node) Node.parentNode.removeChild(Node);