Guest User

Untitled

a guest
Jun 5th, 2018
516
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name            Sangu Package
  3. // @author          Laoujin / De Goede Fee
  4. // @namespace       sangu.be
  5. // @description     Package for Tribal Wars bullies
  6. // @icon            http://www.sangu.be/images/favicon.png
  7. // @include         https://*.tribalwars.nl/game.php?*
  8. // @include         http://sangu.be/*
  9. // @include         http://www.sangu.be/*
  10. // @version         8.128.0
  11. // @grant           GM_xmlhttpRequest
  12. // ==/UserScript==
  13.  
  14. //We are Sangu. You will be assimilated. Resistance is Futile.
  15.  
  16. // The not-one-file source code can be found at:
  17. // https://github.com/SanguPackage/Script
  18.  
  19. function sangu_ready() {
  20.     //var start_time = new Date();
  21.     //console.time("SanguPackage");
  22.     var /**
  23.          * When game_data.majorVersion is different from Sangu version then activate sangu 'compatibility' mode (gray icon)
  24.          */
  25.         sangu_version = '8.128.0',
  26.         /**
  27.          * true: popup with crash dump, false: don't show the popup
  28.          */
  29.         sangu_crash = sangu_version.split(".").length === 4,
  30.         /**
  31.          * jQuery element of the cell (td) that contains all page specific widgets
  32.          */
  33.         content_value = $("#content_value"),
  34.         /**
  35.          * config/ Configuration per server (nl, de). Contains stuff like ajaxAllowed, etc
  36.          */
  37.             server_settings = {},
  38.         /**
  39.          * config/ Contains all translations except for the setting related translations in sangu_trans
  40.          */
  41.         trans = {},
  42.         /**
  43.          * config/ Contains all user settings
  44.          */
  45.         user_data = {},
  46.         /**
  47.          * config/ The current world configuration. settings like hasArchers, nightbonus, etc
  48.          */
  49.          world_config = {},
  50.         /**
  51.          * config/ Contains all data for this world (resources, units, buildings, units_off, unitsSize, ...)
  52.          * What's in this variable depends on world_config.
  53.          * This variable is a complete and utter mess :)
  54.          */
  55.         world_data = {},
  56.         /**
  57.          * Identifies the current page based on the querystring
  58.          */
  59.         current_page = {
  60.             screen: game_data.screen,
  61.             mode: game_data.mode
  62.         },
  63.         keyCodeMap = {
  64.             8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pausebreak", 20: "capslock", 27: "escape", 32: " ",
  65.             33: "pageup", 34: "pagedown", 35: "end", 36: "home", 37: "arrow left", 38: "arrow up", 39: "arrow right", 40: "arrow down", 43: "+",
  66.             44: "printscreen", 45: "insert", 46: "delete", 48: "0", 49: "1", 50: "2", 51: "3", 52: "4", 53: "5", 54: "6", 55: "7", 56: "8", 57: "9",
  67.             59: ";", 61: "=", 65: "a",  66: "b", 67: "c", 68: "d", 69: "e", 70: "f", 71: "g", 72: "h", 73: "i", 74: "j", 75: "k", 76: "l", 77: "m",
  68.             78: "n", 79: "o", 80: "p", 81: "q", 82: "r", 83: "s", 84: "t", 85: "u", 86: "v", 87: "w", 88: "x", 89: "y", 90: "z", 96: "0", 97: "1",
  69.             98: "2", 99: "3", 100: "4", 101: "5",  102: "6", 103: "7", 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111: "/", 112: "f1",
  70.             113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6",  118: "f7", 119: "f8", 120: "f9",  121: "f10", 122: "f11", 123: "f12", 144: "numlock",
  71.             145: "scrolllock", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'"
  72.         };
  73.  
  74.     server_settings = {
  75.         /**
  76.          * Calculate how many more days we can attack in vacation mode
  77.          */
  78.         maxSitDays: 60,
  79.         helpdeskUrl: "https://forum.tribalwars.nl/showthread.php?137674-8-11-GM-Algemeen-discussietopic-Sangu-Package",
  80.         /**
  81.          * This needs to be here for 'historical' reasons (Innogames versionchecker API remembers email on the server)
  82.          * when in 'compatibility' mode (gray sangu icon). Also used in the crash report.
  83.          */
  84.         sangu: "sangu.be",
  85.         sanguEmail: "sangu@pongit.be",
  86.         /**
  87.          * More then 500 [ cannot be sent in messages or pasted in the noteblock
  88.          */
  89.         allowedSquareBrackets: 500,
  90.         /**
  91.          * Are ajax calls allowed on this server
  92.          */
  93.         ajaxAllowed: true,
  94.         /*
  95.          * async: true on AJAX calls is only allowed when this property is true
  96.          */
  97.         asyncAjaxAllowed: false,
  98.         /**
  99.          * True: we add a direct link in the place to fill in coordinates. False: Show coords in an input field
  100.          */
  101.         coordinateLinkAllowed: false,
  102.         /**
  103.          * Can we fill in the coordinates directly in the place (using the url querystring) from troops overview
  104.          */
  105.         autoFillCoordinatesAllowed: true,
  106.         scriptsDatabaseUrl: "http://www.twscripts.nl/"
  107.     };
  108.    
  109.     //$.extend(server_settings, {
  110.     //    extraProperty: "yaye"
  111.     //});
  112.     /**
  113.      * Contains all translation
  114.      * The highest level is split in
  115.      * - tw = translations that should be translated by their server equivalents (they are used somewhere in the html by Innogames)
  116.      * - sp = new translations specific for the Sangu Package
  117.      */
  118.     trans = {
  119.         tw: {
  120.             units: {
  121.                 names: { "spear": "Speer", "sword": "Zwaard", "archer": "Boog", "axe": "Bijl", "spy": "Verk", "light": "Lc", "marcher": "Bb", "heavy": "Zc", "ram": "Ram", "catapult": "Kata", "knight": "Ridder", "snob": "Edel" },
  122.                 twShortNames: { "spear": "Speer", "sword": "Zwaard", "archer": "Boog", "axe": "Bijl", "spy": "Verk.", "light": "Lcav.", "marcher": "Bboog.", "heavy": "Zcav.", "ram": "Ram", "catapult": "Kata.", "knight": "Ridder", "snob": "Edel." },
  123.                 shortNames: { "spear": "Sp", "sword": "Zw", "archer": "Boog", "axe": "Bijl", "spy": "Ver", "light": "Lc", "marcher": "Bb", "heavy": "Zc", "ram": "Ram", "catapult": "Kata", "knight": "Ridder", "snob": "Edel" },
  124.                 militia: "Militia"
  125.             },
  126.             all: {
  127.                 today: "vandaag om",
  128.                 tomorrow: "morgen om",
  129.                 dateOn: "op",
  130.                 timeOn: "om",
  131.                 farm: "Boerderij",
  132.                 wood: "Hout",
  133.                 iron: "IJzer",
  134.                 stone: "Leem",
  135.                 groups: "Groepen",
  136.                 continentPrefix: "C"
  137.             },
  138.             main: {
  139.                 loyaltyHeader: "Toestemming:"
  140.             },
  141.             command: {
  142.                 returnText: "Terugkeer",
  143.                 attack: "Aanval",
  144.                 support: "Ondersteuning",
  145.                 haul: "Buit:",
  146.                 abortedOperation: "Afgebroken commando",
  147.                 catapultTarget: "Katapultdoel:",
  148.                 buttonValue: "OK",
  149.                 attackOn: "Aanval op ",
  150.                 supportFor: "Ondersteuning voor ",
  151.                 walkingTimeTitle: "Duur:"
  152.             },
  153.             incoming: {
  154.                 defaultCommandName: "Bevel"
  155.             },
  156.             place: {
  157.                 troopMovements: "Troepenbewegingen"
  158.             },
  159.             market: {
  160.                 incomingTransports: "Binnenkomende transporten"
  161.             },
  162.             profile: {
  163.                 title: "Profiel",
  164.                 claimedBy: "Geclaimd door:",
  165.                 awardsWon: "Behaalde awards"
  166.             },
  167.             overview: {
  168.                 village: "Dorp",
  169.                 incomingTroops: "Aankomend"
  170.             }
  171.         },
  172.         sp: {
  173.             sp: {
  174.                 settings: {
  175.                     reset: "De standaard Sangu Package settings herstellen",
  176.                     resetAll: "Sangu Package 'fabrieksinstellingen' herstellen",
  177.                     configuration: "Configuratie",
  178.                     configurationFormTogglerTooltip: "Klik op de knoppen om de verschillende editeerschermen te openen",
  179.                     activate: "Activeer",
  180.                     deleteTooltip: "Verwijderen",
  181.                     addRecord: "» Toevoegen",
  182.                     exportSettings: "Instellingen exporteren",
  183.                     importSettings: "Instellingen importeren",
  184.                     importSettingsDesc: "Door de sangu instellingen te exporteren en elders opnieuw te importeren kan je de huidige sangu configuratie hergebruiken op een andere wereld of computer.",
  185.                     importSettingsSuccess: "Settings zijn geïmporteerd!",
  186.                     importError: "Het ziet er naar uit dat de geplakte tekst foutief is.",
  187.                     importErrorContinueAnyway: "Toch importeren?"
  188.                 },
  189.                 donate: {
  190.                     title: "Donatie",
  191.                     whyWouldI: "Als je op regelmatige basis functionaliteit van het Sangu Package gebruikt, dan is dat een goede reden om een donatie te doen."
  192.                                 + "<br><br>"
  193.                                 + "Jouw dankbaarheid en financiële steun helpen me motiveren verder te blijven werken aan het Sangu Package.",
  194.                     books: "Er staan ook een aantal zeer interessante JavaScript boeken op mijn {abegin}Amazon wishlist{aend}. Daarmee kan je me ook altijd een plezier doen :)",
  195.                     notable: "Een aantal personen hebben reeds een notabele donatie gedaan:",
  196.                     buttonAmount: "Doneer €{amount}",
  197.                     beer: "Trakteer me een biertje",
  198.                     food: "Een spaghetti voor Wouter! (of pizza!)",
  199.                     yaye: "Een nieuw IT boek voor mijn verzameling!"
  200.                 },
  201.                 configuration: "Sangu Package configureren (v{version})",
  202.                 activatePackage: "Sangu Package activeren",
  203.                 deactivatePackage: "Sangu Package deactiveren",
  204.                 packageCrashTitle: "Het Sangu Package is gecrasht :)",
  205.                 packageCrashTooltip: "Crash!!! Klik op de bol om het crash rapport te bekijken. "
  206.                     + "Je kan dit rapport naar ons doorsturen waardoor we het probleem mogelijk kunnen oplossen.",
  207.                 packageCrash:
  208.                     "<a href='http://" + server_settings.sangu + "'>Controleer eerst of er een update is!</a><br>"
  209.                     + "<b>Foutmelding</b>: <i>{error}</i><br>"
  210.                     + "<b>Details</b>:<br>"
  211.                     + "<textarea style='width: 95%' rows='20' id='crashArea'>"
  212.                     + "Foutmelding: {error}\n"
  213.                     + "Pagina: {title} -> {page}\n"
  214.                     + "URL: {url}\n"
  215.                     + "Versie: {version}\n"
  216.                     + "Brower: {browser}\n"
  217.                     + "game_data: {game_data}\n"
  218.                     + "\n{stacktrace}\n"
  219.                     + "\n\n\n[HTML]\n"
  220.                     + "</textarea>"
  221.                     + "<br><br>Je kan de bug <a href='{forum-url}' target='_blank'>hier</a> melden."
  222.                     + "<br>Of je kan de bug <a href='mailto:{email}'>mailen</a>."
  223.                     + "<br><br><center><i>Een bug waarvan we niet weten dat ie bestaat zal ook niet gefixed worden!</i></center>"
  224.                     + "<br><center><b>Geef zoveel mogelijk informatie mee bij het sturen van een bugrapport!!!</b></center>",
  225.    
  226.                 activatePackageWithCompatibility: "Sangu Package (v{version}) mogelijk incompatibel met huidige TW versie",
  227.                 firstTimeRun: "<b>Welkom!</b> Het Sangu Package is momenteel inactief. Klik op de nieuwe {img} naast de opslagplaats hierboven om het package aan en uit te schakelen.",
  228.                 firstTimeRunEditSettings: "Klik op de nieuwe 'Sangu Package' link om het package naar jouw smaak in te stellen!",
  229.                 removeScriptWarning: "Niet meer tonen",
  230.                 moreScripts: "Meer scripts",
  231.                 moreScriptsTooltip: "Ga naar de site met alle goedgekeurde TW scripts",
  232.                 sanguLinkTitle: "Het package naar jouw smaak instellen"
  233.             },
  234.             all: {
  235.                 populationShort: "Pop",
  236.                 population: "Populatie",
  237.                 total: "Totaal",
  238.                 last: "Laatste",
  239.                 target: "Doel",
  240.                 targetEx: "Doelwit",
  241.                 more: "meer",
  242.                 less: "minder",
  243.                 all: "Alle",
  244.                 withText: "met",
  245.                 merchants: "Handelaren",
  246.                 further: "verder",
  247.                 closer: "dichter",
  248.                 fieldsSuffix: "(F{0})",
  249.                 close: "Sluiten"
  250.             },
  251.             main: {
  252.                 unitsReplacement: "Eigen",
  253.                 unitsOther: "Ondersteunende Eenheden",
  254.                 rallyPointTroops: "troepen",
  255.                 ownStackTitle: "Totale populatie van de eigen troepen",
  256.                 supportingStackTitle: "Totale populatie van de ondersteunende troepen",
  257.                 showHiddenDivs: "&raquo; Alle verborgen terugzetten ({amount} verborgen)",
  258.                 hideDiv: "Volledig verbergen"
  259.             },
  260.             map: {
  261.                 dodgeLastTagged: "Dodgetijd van de laatst getagde aanval"
  262.             },
  263.             tagger: {
  264.                 openButton: "Open Tagger",
  265.                 rename: "Herbenoemen",
  266.                 renameTooltip: "Alle bevelen waarvan de checkbox aangevinkt is hernoemen",
  267.                 incomingTroops: "Binnenkomende troepen",
  268.                 arrival: "Aankomst",
  269.                 arrivalIn: "Aankomst in",
  270.                 sentNow: "Zojuist",
  271.                 sentSeconds: "seconde(n)",
  272.                 sent1Minute: "1 minuut",
  273.                 sentMinutes: "minuten",
  274.                 sent1Hour: "1 uur",
  275.                 sentHours: "uren",
  276.                 sentOn: "Verstuurtijd",
  277.                 ago: "Geleden",
  278.                 arrivesInNightBonus: " (NACHT!)",
  279.                 tagIt: "Aanval Taggen",
  280.                 checkAllSupport: "Aanvinken van alle zichtbare ondersteuning",
  281.                 uncheckAllSupport: "Uitvinken van alle zichtbare ondersteuning",
  282.                 tagged: "Tagged!",
  283.                 dodgeTime: "Dodgetijd",
  284.                 slowest: "Traagst",
  285.                 slowestTip: "Traagste eenheid in het dorp",
  286.                 allAbove: "Alle vroegere aanvallen aanvinken",
  287.                 allBelow: "Alle latere aanvallen aanvinken",
  288.                 renameTo: "Hernoemen naar: ",
  289.                 switchModus: "&raquo; Alle aanvallen openen/sluiten",
  290.                 checkAllAttacks: "Aanvinken van alle zichtbare aanvallen",
  291.                 uncheckAllAttacks: "Uitvinken van alle zichtbare aanvallen",
  292.                 activeDodgeTime: "Actieve dodgetijd (wordt op de kaart getoond)",
  293.                 totalAttacksOnVillage: "Aantal aanvallen",
  294.                 renameButtonShortcutTooltip: "Shortcut: CTRL + {hitkey}"
  295.             },
  296.             place: {
  297.                 distance: "Afstand",
  298.                 backOn: "Terug op",
  299.                 onlyAttack: "1 aanval op {arrivalDateFirst} ({timeLeftFirst})",
  300.                 multipleAttack: "{amount} aanvallen tussen {arrivalDateFirst} ({timeLeftFirst}) en {arrivalDateLast} ({timeLeftLast})",
  301.                 changeSpeedImageTooltips: "{originalTitle} - Klik om de traagste eenheid te wijzigen"
  302.             },
  303.             jumper: {
  304.                 goToMap: "Ga naar de kaart"
  305.             },
  306.             command: {
  307.                 returnOn: "Terug op:",
  308.                 arrival: "Aankomst",
  309.                 dodgeNotFarEnough: "De dodge is niet ver genoeg!",
  310.                 dodgeMinuteReturn: "(Terugkeer na {minutes})",
  311.                 catapultImageTitle: "Klik om gebouw te vernietigen"
  312.             },
  313.             overviews: {
  314.                 totalVillages: "Aantal dorpen:",
  315.                 loadNextPage: "[volgende pagina laden]"
  316.             },
  317.             troopOverview: {
  318.                 help: "<b>Laat je looptijden uitrekenen door Sangu!</b><br>"
  319.                     + "Gebruik de nieuwe kolom uiterst rechts om de looptijden tot een ingegeven doeldorp te berekenen.<br>"
  320.                     + "<br>Je kan de eenheden icons ({unitIcon}) aanklikken om de snelheid van de traagste eenheid te wijzigen."
  321.                     + "<span style='line-height: 2'>"
  322.                     + "<br>Klikken wijzigt de <span style='border: 2px green dotted'>snelheid op de huidige pagina</span>. "
  323.                     + "<br>Dubbelklikken wijzigt de <span style='border: 3px red solid'>snelheid op alle pagina's</span>."
  324.                     + "</span>"
  325.                     + "<br>De cellen met de groene en rode rand geven de huidige traagst eenheid-snelheid aan."
  326.                     + "<br><br><b>De iconen in de 'Opdracht' kolom</b>:"
  327.                     + "<br>Gebruik <img src='graphic/dots/red.png'> om een rij te verwijderen."
  328.                     + "<br>Gebruik <img src='graphic/command/attack.png'> om naar de verzamelplaats te gaan."
  329.                     + "(gebruik de middelste muisknop om in een nieuwe tab te openen)",
  330.                 helpTitle: "Snel dorpen aanvallen (of ondersteunen)",
  331.                 removeVillage: "Dorp verwijderen",
  332.                 toThePlace: "Verzamelplaats",
  333.                 setTargetVillageButton: "OK",
  334.                 setTargetVillageButtonAlert: "Geef de coördinaten van het dorp dat je wil aanvallen (of ondersteunen)",
  335.                 commandTitle: "Opdracht",
  336.                 selectUnitSpeed: "Selecteer {0} als traagste eenheid. (Klik op deze pagina, Dubbel klik op alle pagina's.)",
  337.                 nightBonus: "Nacht?",
  338.                 village: "Dorp",
  339.                 filterTroops: "Filter",
  340.                 filterTroopsTooltip: "Toon enkel de dorpen met meer dan het aangegeven aantal eenheden",
  341.                 filterPopulation: "Filter populatie",
  342.                 filterPopulationTooltip: "Toon enkel de dorpen met meer/minder bevolking",
  343.                 filterWalkingTime: "Filter looptijd",
  344.                 filterWalkingTimeTooltip: "Toon enkel de dorpen met een langere looptijd (in uren) tot het doeldorp",
  345.                 calcStack: "Bereken stack",
  346.                 calcStackTooltip: "Toon de bevolking per dorp in de \"Nacht?\" kolom",
  347.                 filterNoble: "Toon edels",
  348.                 filterNobleTooltip: "Toon enkel de dorpen waar edels aanwezig zijn",
  349.                 filterUnderAttack: "Toon onder aanval",
  350.                 filterUnderAttackTooltip: "Toon enkel de dorpen die onder aanval zijn",
  351.                 sort: "Sorteren",
  352.                 sortTooltip: "Sorteren op looptijd tot doeldorp",
  353.                 restack: "Stack BB Codes",
  354.                 restackTitle: "<b>Alle dorpen met minstens {requiredDiff}k bevolking minder dan {to}k</b><br>"
  355.                     + "<font size='-2'>(configuratie wijzigbaar bij Sangu instellingen)</font>",
  356.                 cheapNobles: "Goedkope edelmannen beschikbaar",
  357.    
  358.                 filtersReverse: "De filtering omdraaien",
  359.                 filtersReverseInfo: "Filters omdraaien",
  360.                 freeTextFilter: "Tekst filter",
  361.                 freeTextFilterTooltip: "Dorpen {filterType} de tekst wegfilteren",
  362.                 freeTextFilterTooltipFilterTypeWith: "met",
  363.                 freeTextFilterTooltipFilterTypeWithout: "zonder",
  364.                 continentFilter: "Continent",
  365.                 continentFilterTooltip: "Alle dorpen in continent wegfilteren",
  366.                 continentFilterTooltipReverse: "Alle dorpen in continent tonen"
  367.             },
  368.             prodOverview: {
  369.                 filter: "Filter",
  370.                 filterFullGS: "Volle opslag",
  371.                 merchantTooltip: "Vink aan om handelaren te highlighten",
  372.                 merchantAmountTooltip: "Als de checkbox aangevinkt is worden dorpen met minder dan x handelaren in het rood gehighlight",
  373.                 bbCodes: "BB Codes",
  374.                 bbCodesInfo: "Gebruik IMG",
  375.                 filterTooltip: "Dorpen die niet aan de filtercriteria voldoen verbergen",
  376.                 filterTooltipReverse: "Dorpen die voldoen aan de filtercriteria highlighten",
  377.                 filterFullGSTooltip: "Dorpen waarbij niet minstens 1 van de grondstoffen vol is verbergen",
  378.                 filterFullGSTooltipReverse: "Dorpen waarbij minstens 1 van de grondstoffen vol is highlighten",
  379.                 filterAllTooltip: "Dorpen waarbij niet minstens 1 van de grondstoffen meer/minder dan x is verbergen",
  380.                 filterAllTooltipReverse: "Dorpen waarbij minstens 1 van de grondstoffen meer/minder dan x is highlighten",
  381.                 filter1Tooltip: "Dorpen waarbij er nier meer/minder dan x {0} is verbergen",
  382.                 filter1TooltipReverse: "Dorpen waarbij er meer/minder dan x {0} is highlighten",
  383.                 tooMuch: "Teveel:",
  384.                 tooMuchText: "Alle dorpen met minstens {diff}k meer grondstoffen dan {min}k",
  385.                 tooLittle: "Te weinig:",
  386.                 tooLittleText: "Alle dorpen met minstens {diff}k minder grondstoffen dan {min}k",
  387.                 bbCodeExtraInfo: "<br><font size='-2'>(configuratie wijzigbaar bij Sangu instellingen)</font>"
  388.             },
  389.             buildOverview: {
  390.                 optimistic: "Optimistisch",
  391.                 mark: "Duiden",
  392.                 filter: "Filteren"
  393.             },
  394.             smithOverview: {
  395.                 optimistic: "Optimistisch",
  396.                 mark: "Duiden",
  397.                 filter: "Filteren"
  398.             },
  399.             defOverview: {
  400.                 stackButton: "Totalen berekenen",
  401.                 stackTooltip: "Totale stack en afstanden berekenen",
  402.                 stackFilter: "Filter op stack",
  403.                 stackFilterTooltip: "Filter dorpen met meer/minder dan x totale stack vanbuiten het dorp",
  404.                 village: "Dorp:",
  405.                 distFilter: "Filter op afstand",
  406.                 distFilterTooltip: "Filter alle dorpen die verder/dichter dan x velden liggen van dorp y",
  407.                 stackBBCodes: "Stack BBCodes",
  408.                 stackBBCodesTooltip: "Bepaal BB codes en aantal troepen voor een stack tot x populatie voor alle zichtbare dorpen",
  409.                 filterNoSupport: "Zonder OS wegfilteren",
  410.                 filterNoSupportTooltip: "Wegfilteren van alle dorpen waar geen ondersteuning meer zichtbaar is",
  411.                 extraFiltersSupport: "Ondersteunende dorpen filters:",
  412.                 extraFiltersDefense: "Ondersteuning filters:",
  413.                 extraFiltersReverse: "De filtering omdraaien",
  414.                 extraFiltersInfo: "Filters omdraaien",
  415.                 distFilter2: "Afstand filter",
  416.                 freeTextFilter: "Tekst filter",
  417.                 barbarianFilter: "Barbarendorpen",
  418.                 barbarianFilterTooltip: "Toon alle ondersteuningen naar barbarendorpen",
  419.                 nobleFilter: "Alle edel-ondersteuning tonen",
  420.                 nobleFilterRev: "Alle edel-ondersteuning wegfilteren",
  421.                 spyFilter: "Alle verkenner-ondersteuning tonen",
  422.                 spyFilterRev: "Alle verkenner-ondersteuning wegfilteren",
  423.                 attackFilter: "Alle aanval-ondersteuning tonen",
  424.                 attackFilterRev: "Alle aanval-ondersteuning wegfilteren",
  425.                 supportFilter: "Alle verdediging-ondersteuning tonen",
  426.                 supportFilterRev: "Alle verdediging-ondersteuning wegfilteren",
  427.                 otherPlayerFilterShow: "tonen",
  428.                 otherPlayerFilterHide: "wegfilteren",
  429.                 otherPlayerFilterTo: "Alle ondersteuningen naar andere spelers {action}",
  430.                 otherPlayerFilterFrom: "Alle ondersteuningen van andere spelers {action}",
  431.    
  432.                 filterTooltipVillageTypeSupporting: "Ondersteunende dorpen",
  433.                 filterTooltipVillageTypeSupported: "Ondersteunde dorpen",
  434.                 freeTextFilterTooltip: "{villageType} {filterType} de tekst wegfilteren",
  435.                 freeTextFilterTooltipFilterTypeWith: "met",
  436.                 freeTextFilterTooltipFilterTypeWithout: "zonder",
  437.                 distanceFilterTooltip: "{villageType} die {filterType} dan het aangegeven aantal velden liggen wegfilteren",
  438.                 distanceFilterTooltipFilterTypeCloser: "dichter",
  439.                 distanceFilterTooltipFilterTypeFurther: "verder",
  440.    
  441.                 totalFromOtherVillages: "totaal uit andere dorpen",
  442.                 totalInOtherVillages: "totaal in andere dorpen",
  443.                 freeText: "Vrij tekstveld (wordt niet opgeslagen!):",
  444.                 fieldsPrefix: "F{0}",
  445.                 thousandSuffix: "k",
  446.                 totalVillages: "Dorpen ({0})",
  447.                 distanceToVillageNoneEntered: "Geef een coördinaat! (eerste tekstveld)",
  448.                 distanceToVillage: "Afstand tot {0}",
  449.                 filterUnderAttack: "Filter onder aanval"
  450.             },
  451.             commands: {
  452.                 filterReturn: "Filter terugkeer",
  453.                 filterReturnTooltip: "'Teruggestuurd' en 'Terugkeer' bevelen verbergen",
  454.                 totalRows: "Somlijn",
  455.                 group: "Groeperen",
  456.                 totalRowsText: "{0}x OS = {1} pop",
  457.                 totalVillagesSupport: "Ondersteunde dorpen:",
  458.                 totalVillagesAttack: "Aangevallen dorpen:",
  459.                 totalSupport: "Ondersteuningen",
  460.                 totalAttack: "Aanvallen",
  461.                 bbCodeExport: "BBCode Export",
  462.                 bbCodeExportTooltip: "Overblijvende aanvallen exporteren",
  463.                 supportPlayerExport: "Ondersteuning exporteren",
  464.                 supportPlayerExportTooltip: "Geef de naam van de speler waarvoor je de ondersteuning wil exporteren "
  465.                     + "(of laat leeg om alle ondersteuningen te exporteren). Door de export als mededeling "
  466.                     + "naar de andere speler door te sturen "
  467.                     + "kan deze jouw gestuurde troepen als bevelnaam zetten. (Hij heeft daarvoor natuurlijk"
  468.                     + " ook het Sangu Package nodig :)",
  469.                 filtersReverse: "De filtering omdraaien",
  470.                 filtersReverseInfo: "Filters omdraaien",
  471.                 freeTextFilter: "Tekst filter",
  472.                 freeTextFilterTooltip: "Aanvallen {filterType} de tekst wegfilteren",
  473.                 freeTextFilterTooltipFilterTypeWith: "met",
  474.                 freeTextFilterTooltipFilterTypeWithout: "zonder",
  475.                 nobleFilter: "Alle edelaanvallen tonen",
  476.                 nobleFilterRev: "Alle edelaanvallen wegfilteren",
  477.                 spyFilter: "Alle verkenneraanvallen tonen",
  478.                 spyFilterRev: "Alle verkenneraanvallen wegfilteren",
  479.                 tableTotal: "Bevel ({0})",
  480.                 fakeFilter: "Alle fake aanvallen wegfilteren",
  481.                 fakeFilterRev: "Alle fake aanvallen tonen",
  482.                 continentFilter: "Continent",
  483.                 continentFilterTooltip: "Alle dorpen in continent wegfilteren",
  484.                 continentFilterTooltipReverse: "Alle dorpen in continent tonen",
  485.                 exportAttackHeader: "{village} {#} aanvallen, laatste [b]{lastAttack}[/b]",
  486.                 exportDefenseHeader: "{village} {support#} ondersteuningen voor [b]{totalStack} pop[/b]",
  487.                 exportCompleteHeader: "{village} {#} aanvallen, laatste [b]{lastAttack}[/b]\n+ {support#} ondersteuningen voor [b]{totalStack} pop[/b]",
  488.                 exportNone: "Geen ondersteuning gevonden!"
  489.             },
  490.             groups: {
  491.                 villageFilter: "Dorpsnaam",
  492.                 villageFilterTitle: "Alle dorpen met de tekst in de dorpsnaam wegfilteren",
  493.                 villageFilterTitleRev: "Alle dorpen met de tekst in de dorpsnaam tonen",
  494.                 pointsFilter: "Punten",
  495.                 amountFilter: "Aantal",
  496.                 groupNameFilter: "Groepsnaam",
  497.                 amountFilterTitle: "Alle dorpen met minder groepen wegfilteren",
  498.                 amountFilterTitleRev: "Alle dorpen met meer groepen wegfilteren",
  499.                 pointsFilterTitle: "Alle dorpen met minder punten wegfilteren",
  500.                 pointsFilterTitleRev: "Alle dorpen met meer punten wegfilteren",
  501.                 farmFilterTitle: "Alle dorpen met minder populatie wegfilteren",
  502.                 farmFilterTitleRev: "Alle dorpen met meer populatie wegfilteren",
  503.                 groupNameFilterTitle: "Alle dorpen met de tekst in een groepsnaam wegfilteren",
  504.                 groupNameFilterTitleRev: "Alle dorpen met de tekst in een groepsnaam tonen"
  505.             },
  506.             snob: {
  507.                 canProduce: "Je kan meteen produceren:"
  508.             },
  509.             profile: {
  510.                 twStatsMap: "TWStats Kaart",
  511.                 externalPage: "(Extern)",
  512.                 internalPage: "(Intern)",
  513.                 conquers: "Overnames",
  514.                 villages: "Dorpen:",
  515.                 graphPoints: "Punten",
  516.                 graphVillages: "Dorpen",
  517.                 graphOD: "OD Totaal",
  518.                 graphODD: "OD Verdediging",
  519.                 graphODA: "OD Aanval",
  520.                 graphODS: "OD Ondersteuning",
  521.                 graphRank: "Rang",
  522.                 graphMembers: "Leden",
  523.                 graphTWMap: "TribalWarsMap.com"
  524.             },
  525.             incomings: {
  526.                 dynamicGrouping: "Dynamisch groeperen",
  527.                 dynamicGroupingTooltip: "Groepeert trager maar bevriest de pagina niet",
  528.                 summation: "Somlijn",
  529.                 fastGrouping: "Snel groeperen",
  530.                 fastGroupingTooltip: "Groepeert sneller en bevriest de pagina (geen refreshs wanneer een aanval binnenkomt)",
  531.                 showNewIncomings: "Toon nieuwe aanvallen",
  532.                 sortByAttackId: "Sorteer op aanvalsid",
  533.                 sortByAttackIdTooltip: "Een kleiner aanvalsid betekent een eerder verstuurde aanval",
  534.                 amount: "Aanvallen:",
  535.                 attackId: "Aanvalsid",
  536.                 attackIdDifference: "Verschil",
  537.                 filterColumnButton: "Kolom filter",
  538.                 filterColumnButtonTooltip: "Alle bevelen waarbij de geselecteerde kolom de ingegeven tekst bevat {type}",
  539.                 filterColumnButtonTooltipHide: "verbergen",
  540.                 filterColumnButtonTooltipShow: "tonen",
  541.    
  542.                 indicator: {
  543.                     lastTimeCheckHintBoxTooltip: "Klik op {img} om de laatste tijdcheck met de huidige tijd te vervangen.",
  544.                     lastTimeCheckNotYetSet: "(nog niet)"
  545.                 },
  546.                 commandsImport: "Ondersteuning importeren",
  547.                 commandsImportTooltip: "Ondersteuning naar jouw dorpen kan op jouw account ingelezen worden"
  548.                     + " door de andere speler zijn bevelen te laten exporteren via Sangu Package (Pagina Overzicht: Bevelen)"
  549.                     + " en die export hier te plakken.",
  550.                 commandsImportError: "Fout bij het inladen van de ondersteuningen.\nHet zou er zoals dit moeten uitzien: \n"
  551.                     + '[{"commandName": "702|459 (speler) Zw=1 (Pop: 1)", "commandId": "7538763"}]',
  552.                 commandsImportSuccess: "{replaced} van de {total} ondersteuningen zijn hernoemd."
  553.             },
  554.             rest: {
  555.                 sittingAttackTill: "Aanvallen en verdedigen van dorpen niet in eigen beheer tot:",
  556.                 friendsOnline: "Vrienden {friends} ({onlineimg} {online#} | {offlineimg} {offline#})",
  557.                 friendsOnlineTitle: "Online: {playerNames}"
  558.             }
  559.         }
  560.     };
  561.  
  562.         /**
  563.          * Log the parameter to the console (print yaye when undefined)
  564.          */
  565.         function q(what) { console.log(typeof what === "undefined" ? "yaye" : what); }
  566.        
  567.         /**
  568.          * Alert the parameter (yaye when undefined)
  569.          */
  570.         function qa(what) { alert(typeof what === "undefined" ? "yaye" : what); }
  571.        
  572.         /**
  573.          * Show crash report
  574.          */
  575.         function sangu_alert(e, title) {
  576.             var activator = $("#sangu_activator");
  577.        
  578.             activator
  579.                 .attr("src", "graphic/dots/grey.png")
  580.                 .attr("title", trans.sp.sp.packageCrashTooltip);
  581.        
  582.             (function() {
  583.                 var position = $("#storage").position(),
  584.                     options = {
  585.                         left: position.left - 150,
  586.                         top: position.top + 35
  587.                     },
  588.                     content = {body: trans.sp.sp.packageCrashTooltip, title: trans.sp.sp.packageCrashTitle};
  589.        
  590.                 createFixedTooltip("sanguCrashTooltip", content, options);
  591.             }());
  592.        
  593.             activator.click(function() {
  594.                 var currentPageHtml = document.documentElement.innerHTML,
  595.                     position = $("#storage").position(),
  596.                     options = {
  597.                         left: $(window).width() / 2 - 300,
  598.                         top: position.top + 35,
  599.                         width: 600,
  600.                         showOnce: false
  601.                     },
  602.                     game_dataSubset = {
  603.                         majorVersion: game_data.majorVersion,
  604.                         market: game_data.market,
  605.                         world: game_data.world,
  606.                         sitter: game_data.player.sitter,
  607.                         village_id: game_data.village.id,
  608.                         player_id: game_data.player.id,
  609.                         player_name: game_data.player.name,
  610.                         ally_id: game_data.player.ally_id,
  611.                         villages: game_data.player.villages,
  612.                         premium: game_data.features.Premium.active/*,
  613.                          account_manager: game_data.features.AccountManager.active,
  614.                          farm_manager: game_data.features.FarmAssistent.active*/
  615.                     },
  616.                     content = {
  617.                         title: trans.sp.sp.packageCrashTitle,
  618.                         body: trans.sp.sp.packageCrash
  619.                             .replace("{forum-url}", server_settings.helpdeskUrl)
  620.                             .replace("{title}", title)
  621.                             .replace(/\{error\}/g, e.message)
  622.                             .replace("{page}", JSON.stringify(current_page))
  623.                             .replace("{url}", document.location.href)
  624.                             .replace("{version}", sangu_version)
  625.                             .replace("{browser}", JSON.stringify($.browser))
  626.                             .replace("{game_data}", JSON.stringify(game_dataSubset))
  627.                             .replace("{stacktrace}", e.stack ? e.stack + "\n\n" + e.stacktrace : "assertion?")
  628.                             .replace("{email}", server_settings.sanguEmail)
  629.                             .replace("{html}", currentPageHtml)
  630.                     };
  631.        
  632.                 createFixedTooltip("sanguCrash", content, options);
  633.                 $("#crashArea").val($("#crashArea").val() + currentPageHtml);
  634.        
  635.                 return false;
  636.             });
  637.        
  638.             for(i = 0; i < 7; i++) {
  639.                 activator.fadeTo('slow', 0.2).fadeTo('slow', 1.0);
  640.             }
  641.             if (sangu_crash) {
  642.                 activator.click();
  643.             }
  644.         }
  645.        
  646.         /**
  647.          * Failed assertions show the crash report!
  648.          */
  649.         function assert(shouldBeTruthy, message) {
  650.             if (!shouldBeTruthy) {
  651.                 sangu_alert({message: message || "(broken assertion)"});
  652.             }
  653.         }
  654.        
  655.         /**
  656.          * Show crash report
  657.          */
  658.         function handleException(e, title) {
  659.             sangu_alert(e, title);
  660.         }
  661.     function pad(number, length) {
  662.         var str = '' + number;
  663.         while (str.length < length) {
  664.             str = '0' + str;
  665.         }
  666.         return str;
  667.     }
  668.    
  669.     /**
  670.      * Gets a value from the querystring (or returns "")
  671.      * @param {string} name the name of the querystring parameter
  672.      * @param {string} [url] when omitted, the current location.url is assumed
  673.      */
  674.     function getQueryStringParam(name, url) {
  675.         name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
  676.         var regexS = "[\\?&]" + name + "=([^&#]*)";
  677.         var regex = new RegExp(regexS);
  678.         if( url == undefined && name == "village" ) {
  679.             return game_data.village.id;
  680.         } else {
  681.             var results = regex.exec(url == undefined ? window.location.href : url);
  682.             if (results == null) {
  683.                 return "";
  684.             } else {
  685.                 return results[1];
  686.             }
  687.         }
  688.     }
  689.    
  690.     /**
  691.      * Get a TW url taking account sitting etc in account
  692.      * @param {string} url provide the url starting from &screen=
  693.      * @param {number} [villageId] when omitted the current village is assumed
  694.      */
  695.     function getUrlString(url, villageId) {
  696.         if (url.indexOf("?") == -1) {
  697.             var link = location.href.substr(0, location.href.indexOf("?"));
  698.             link += "?village=" + (villageId ? villageId : getQueryStringParam("village"));
  699.             var isSit = getQueryStringParam("t");
  700.             if (isSit) {
  701.                 link += "&t=" + isSit;
  702.             }
  703.    
  704.             if (url.indexOf("=") == -1) {
  705.                 return link + "&screen=" + url;
  706.             } else {
  707.                 return link + "&" + url;
  708.             }
  709.         } else {
  710.             return url;
  711.         }
  712.     }
  713.    
  714.     /**
  715.      * Perform an ajax call (if the server allows it)
  716.      * @param {string} screen passed to getUrlString. (start from &screen=)
  717.      * @param {function} strategy executed on success. Has parameter text (content of the parameter depends on opts.contentValue)
  718.      * @param {object} [opts] object with properties
  719.      *              {false|number} [villageId] passed to getUrlString. Default is false which defaults to current village. Otherwise pass a village id.
  720.      *              {boolean=true} [contentValue] true (default): only return the #content_value. false: return entire DOM HTML
  721.      *              {boolean=true} [async] defaults to true
  722.      */
  723.     function ajax(screen, strategy, opts) {
  724.         if (!server_settings.ajaxAllowed) {
  725.             alert("Ajax is not allowed on this server.");
  726.             return;
  727.         }
  728.    
  729.         opts = $.extend({}, { villageId: false, contentValue: true, async: false }, opts);
  730.    
  731.         $.ajax({
  732.             url: getUrlString(screen, opts.villageId),
  733.             async: server_settings.asyncAjaxAllowed ? opts.async : false,
  734.             success: function(text) {
  735.                 text = opts.contentValue ? $("#content_value", text) : text;
  736.                 strategy(text);
  737.             }
  738.         });
  739.     }
  740.    
  741.     function spSpeedCookie(setter) {
  742.         if (setter == undefined) {
  743.             var speedCookie = pers.get("targetVillageSpeed");
  744.             if (speedCookie == '') {
  745.                 speedCookie = 'ram';
  746.             }
  747.             return speedCookie;
  748.         } else {
  749.             if (setter.indexOf('_') == 4) {
  750.                 setter = setter.substr(setter.indexOf('_') + 1);
  751.             }
  752.             pers.set("targetVillageSpeed", setter);
  753.             return setter;
  754.         }
  755.     }
  756.    
  757.     function spTargetVillageCookie(setter) {
  758.         if (setter == undefined) {
  759.             return pers.get("targetVillageCoord");
  760.         } else {
  761.             pers.set("targetVillageCoord", setter);
  762.             return setter;
  763.         }
  764.     }
  765.    
  766.     function getDistance(x1, x2, y1, y2, speed) {
  767.         var dist = {};
  768.         dist.fields = Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
  769.         dist.travelTime = dist.fields * (speed == '' ? world_data.unitsSpeed.unit_ram : world_data.unitsSpeed['unit_' + speed]);
  770.         dist.arrivalTime = getDateFromTW($("#serverTime").text(), true);
  771.         dist.arrivalTime.setTime(dist.arrivalTime.getTime() + (dist.travelTime * 60 * 1000));
  772.         dist.isNightBonus = isDateInNightBonus(dist.arrivalTime);
  773.    
  774.         if (speed == 'snob' && dist.travelTime > world_config.maxNobleWalkingTime) {
  775.             dist.html = "<font color='" + user_data.colors.error + "'><b>" + twDurationFormat(dist.travelTime) + "</b></font>";
  776.             dist.isNightBonus = true;
  777.         } else {
  778.             var displayTime = twDateFormat(dist.arrivalTime);
  779.             if (speed != 'merchant' && dist.isNightBonus) {
  780.                 displayTime = "<font color='" + user_data.colors.error + "'><b>" + displayTime + "</b></font>";
  781.             }
  782.             dist.html = user_data.walkingTimeDisplay
  783.                 .replace("{duration}", twDurationFormat(dist.travelTime))
  784.                 .replace("{arrival}", displayTime);
  785.         }
  786.         if (dist.fields == 0) {
  787.             dist.html = "";
  788.         }
  789.    
  790.         return dist;
  791.     }
  792.    
  793.     // _gaq.push(['b._setAccount', 'UA-30075487-3']);
  794.    
  795.     /**
  796.      * Send click to google analytics
  797.      * @param {string} action
  798.      */
  799.     function trackClickEvent(action) {
  800.         trackEvent("ButtonClick", action);
  801.     }
  802.    
  803.     /**
  804.      * google analytics event tracking
  805.      */
  806.     function trackEvent(category, action, label) {
  807.         // category: clicks (downloads, ...)
  808.         // action: which button clicked
  809.         if (typeof label === 'undefined') {
  810.             label = getQueryStringParam("screen");
  811.             var mode = getQueryStringParam("mode");
  812.             if (mode) label += "-" + mode;
  813.         }
  814.    
  815.         //_gaq.push(['b._setAccount', 'UA-30075487-3']);
  816.         //_gaq.push(['b._trackPageview']);
  817.         // _gat._getTrackerByName('b')._trackEvent("SanguPackage", "Loaded", "withGetB");
  818.      //    try
  819.      //    {
  820.      //        _gat._getTrackerByName('b')._trackEvent(category, action, label);
  821.      //    }
  822.         // catch (e) {
  823.      //        // no crash report for this
  824.      //    }
  825.     }
  826.    
  827.     function fillRallyPoint(units) {
  828.         var script = "";
  829.         $.each(world_data.units, function (i, v) {
  830.             if (units[v] != undefined && units[v] > 0) {
  831.                 script += "document.forms[0]." + v + ".value=\"" + units[v] + "\";";
  832.             } else {
  833.                 script += "document.forms[0]." + v + ".value=\"\";";
  834.             }
  835.         });
  836.    
  837.         return script;
  838.     }
  839.    
  840.     /**
  841.      * Tries to find village coords in str and convert it to a 'village' object
  842.      * @param {string} str the string to be converted to a village object
  843.      * @param {true|} looseMatch !!!Do not provide a value for looseMatch when converting a real village name.!!!
  844.      *          It should be set to true when it was the user that provided the input str. When true,
  845.      *          a str like 456-789 would also match. (so that Sangu Package users don't have to use | but can instead
  846.      *          use anything to seperate the 2 coordinates).
  847.      * @returns {object} object with parameters isValid: false when the string could not be matched and true with extra properties
  848.      * x, y, coord, validName (=html friendly id name) and continent
  849.      */
  850.     function getVillageFromCoords(str, looseMatch) {
  851.         // if str is "villageName (X|Y) C54" then the villageName could be something like "456-321"
  852.         // the regex then thinks that the villageName are the coords
  853.         // looseMatch
  854.         var targetMatch = looseMatch != undefined ? str.match(/(\d+)\D(\d+)/g) : str.match(/(\d+)\|(\d+)/g);
  855.         if (targetMatch != null && targetMatch.length > 0) {
  856.             var coordMatch = targetMatch[targetMatch.length - 1].match(/(\d+)\D(\d+)/);
  857.             var village = { "isValid": true, "coord": coordMatch[1] + '|' + coordMatch[2], "x": coordMatch[1], "y": coordMatch[2] };
  858.    
  859.             village.validName = function () { return this.x + '_' + this.y; };
  860.             village.continent = function () { return this.y.substr(0, 1) + this.x.substr(0, 1); };
  861.    
  862.             return village;
  863.         }
  864.         return { "isValid": false };
  865.     }
  866.    
  867.     function buildAttackString(villageCoord, unitsSent, player, isSupport, minimum, haulDescription) {
  868.         var seperator = " ";
  869.         if (minimum == undefined) {
  870.             minimum = 0;
  871.         }
  872.    
  873.         var totalPop = 0;
  874.         var renamed = villageCoord == null ? "" : villageCoord + seperator;
  875.         var sent = "";
  876.         $.each(world_data.units, function (i, val) {
  877.             var amount = unitsSent[val];
  878.             if (amount != 0) {
  879.                 if (val == "snob") {
  880.                     renamed += trans.tw.units.names[val] + "! ";
  881.                 }
  882.                 else if (amount >= minimum) {
  883.                     sent += ", " + trans.tw.units.shortNames[val] + "=" + amount;
  884.                 }
  885.    
  886.                 totalPop += amount * world_data.unitsPositionSize[i];
  887.             }
  888.         });
  889.    
  890.         if (player) {
  891.             renamed += '(' + player + ')' + seperator;
  892.         }
  893.         if (sent.length > 2) {
  894.             sent = sent.substr(2);
  895.         }
  896.    
  897.         if (isSupport) {
  898.             sent += seperator + "(" + trans.sp.all.populationShort + ": " + formatNumber(totalPop) + ")";
  899.         }
  900.    
  901.         if (user_data.attackAutoRename.addHaul && typeof haulDescription !== 'undefined') {
  902.             sent += " (" + trans.tw.command.haul + " " + haulDescription + ")";
  903.         }
  904.    
  905.         return renamed + sent;
  906.     }
  907.    
  908.     function calcTroops(units) {
  909.         // units is an array of numbers; keys are the unit names (without unit_)
  910.         var x = {};
  911.         x.totalDef = 0;
  912.    
  913.         function removeElement(arr, element) {
  914.             var idx = arr.indexOf(element);
  915.             if (idx != -1) {
  916.                 arr.splice(idx, 1);
  917.             }
  918.             return arr;
  919.         }
  920.    
  921.         // heavy doesn't count in determining whether the village is def/off (since you got some crazy guys using hc as offense and defense:)
  922.         $.each(removeElement(world_data.units_def, 'heavy'), function (i, v) { x.totalDef += units[v] * world_data.unitsSize['unit_' + v]; });
  923.         x.totalOff = 0;
  924.         $.each(removeElement(world_data.units_off, 'heavy'), function (i, v) { x.totalOff += units[v] * world_data.unitsSize['unit_' + v]; });
  925.    
  926.         x.isDef = x.totalDef > x.totalOff;
  927.         x.isScout = units.spy * world_data.unitsSize.unit_spy > x.totalDef + x.totalOff;
  928.         x.isMatch = function (type) { return (type == 'all' || (type == 'def' && this.isDef) || (type == 'off' && !this.isDef)); };
  929.    
  930.         x.getSlowest =
  931.             function () {
  932.                 var slowest_unit = null;
  933.                 $.each(world_data.units, function (i, v) {
  934.                     if (units[v] > 0 && (slowest_unit == null || world_data.unitsSpeed["unit_" + slowest_unit] < world_data.unitsSpeed["unit_" + v])) {
  935.                         slowest_unit = v;
  936.                     }
  937.                 });
  938.                 return slowest_unit;
  939.             };
  940.    
  941.         x.colorIfNotRightAttackType =
  942.             function (cell, isAttack) {
  943.                 var isSet = false;
  944.                 if (units.snob != undefined && units.snob > 0) {
  945.                     if (isAttack) {
  946.                         if (units.snob > 1) {
  947.                             isSet = true;
  948.                             cell.css("background-color", user_data.colors.error).css("border", "1px solid black");
  949.                             cell.animate({
  950.                                     width: "70%",
  951.                                     opacity: 0.4,
  952.                                     marginLeft: "0.6in",
  953.                                     fontSize: "3em",
  954.                                     borderWidth: "10px"
  955.                                 }, 5000, function () {
  956.                                     // Animation complete.
  957.                             });
  958.                         } else {
  959.                             return;
  960.                         }
  961.                     } else {
  962.                         isSet = true;
  963.                     }
  964.                 }
  965.                 else if (x.totalDef + x.totalOff < user_data.command.filterFakeMaxPop) {
  966.                     // fake
  967.                     return;
  968.                 }
  969.    
  970.                 if (!isSet && (x.isScout || x.isMatch(isAttack ? 'off' : 'def'))) {
  971.                     return;
  972.                 }
  973.                 cell.css("background-color", user_data.colors.error);
  974.             };
  975.    
  976.         return x;
  977.     }
  978.    
  979.    
  980.     function stackDisplay(totalFarm, stackOptions) {
  981.         // TODO: this function is only used on main village overview
  982.         if (stackOptions == undefined) {
  983.             stackOptions = {};
  984.         }
  985.         var farmSize = game_data.village.buildings.farm * world_config.farmLimit;
  986.    
  987.         var stackDesc = '<b>' + formatNumber(totalFarm);
  988.         if (stackOptions.showFarmLimit && world_config.farmLimit > 0) {
  989.             stackDesc += ' / ' + formatNumber(farmSize);
  990.         }
  991.    
  992.         if (stackOptions.percentage) {
  993.             stackDesc += ' (' + stackOptions.percentage + ')</b>';
  994.         }
  995.    
  996.         var bgColor = getStackColor(totalFarm, farmSize);
  997.         if (stackOptions.cell == undefined) {
  998.             return {
  999.                 color: bgColor,
  1000.                 desc: stackDesc,
  1001.                 cssColor: "style='background-color:" + bgColor + "'"
  1002.             };
  1003.         } else {
  1004.             if (stackOptions.appendToCell) {
  1005.                 stackOptions.cell.append(" &raquo; " + stackDesc);
  1006.             } else {
  1007.                 stackOptions.cell.html(stackDesc);
  1008.             }
  1009.             if (!stackOptions.skipColoring) {
  1010.                 stackOptions.cell.css("background-color", bgColor);
  1011.             }
  1012.         }
  1013.     }
  1014.    
  1015.     /**
  1016.      * Gets the configured stack backgroundcolor for the given stackTotal
  1017.      * @param stackTotal {number}
  1018.      * @returns {color}
  1019.      */
  1020.     function getStackColor(stackTotal) {
  1021.         var color = null,
  1022.             arrayToIterate,
  1023.             farmLimitModifier;
  1024.    
  1025.         if (world_config.farmLimit > 0) {
  1026.             arrayToIterate = user_data.farmLimit.acceptableOverstack;
  1027.             farmLimitModifier = 30 * world_config.farmLimit;
  1028.         } else {
  1029.             arrayToIterate = user_data.farmLimit.unlimitedStack;
  1030.             farmLimitModifier = 1; // = No modifier
  1031.         }
  1032.    
  1033.         if (arrayToIterate.length > 0) {
  1034.             $.each(arrayToIterate, function (index, configValue) {
  1035.                 if (color == null && stackTotal < farmLimitModifier * configValue) {
  1036.                     if (index === 0) {
  1037.                         color = "";
  1038.                     } else {
  1039.                         color = user_data.farmLimit.stackColors[index - 1];
  1040.                         //q(stackTotal +"<"+ farmLimitModifier +"*"+ configValue);
  1041.                         //q("return " + (index - 1) + "->" + color);
  1042.                     }
  1043.                     return false;
  1044.                 }
  1045.             });
  1046.    
  1047.             if (color != null) {
  1048.                 //q(stackTotal + " -> " + color);
  1049.                 return color;
  1050.             }
  1051.             return user_data.farmLimit.stackColors[user_data.farmLimit.stackColors.length - 1];
  1052.         }
  1053.    
  1054.         return "";
  1055.     }
  1056.     var modernizr = (function () {
  1057.         // Difference in capital letter with the Modernizr library
  1058.         // So nothing will break should TW start making use of it
  1059.         return {
  1060.             localstorage: (function supports_html5_storage() {
  1061.                 try {
  1062.                     return 'localStorage' in window && window['localStorage'] !== null;
  1063.                 } catch (e) {
  1064.                     return false;
  1065.                 }
  1066.             })()
  1067.         };
  1068.     })();
  1069.    
  1070.     var pers;
  1071.     (function (pers) {
  1072.         function getKey(key) {
  1073.             return 'sangu_' + key;
  1074.         }
  1075.    
  1076.         function getWorldKey(key) {
  1077.             return 'sangu_' + game_data.world + '_' + key;
  1078.         }
  1079.    
  1080.         function getCookie(key) {
  1081.             key = getWorldKey(key);
  1082.             return (function() {
  1083.                 var x, cooks, cookie;
  1084.                 if (document.cookie.match(/;/)) {
  1085.                     cooks = document.cookie.split("; ");
  1086.                     for (x = 0; x < cooks.length; x++) {
  1087.                         cookie = cooks[x];
  1088.                         if (cookie.match(key + "=")) {
  1089.                             return cookie.replace(key + "=", "");
  1090.                         }
  1091.                     }
  1092.                 } else {
  1093.                     if (document.cookie.match(key + "=")) {
  1094.                         return document.cookie.replace(key + "=", "");
  1095.                     }
  1096.                 }
  1097.    
  1098.                 return '';
  1099.             })();
  1100.         }
  1101.        
  1102.         function getGlobal(key) {
  1103.             key = getKey(key);
  1104.             if (modernizr.localstorage) {
  1105.                 var value = localStorage[key];
  1106.                 return typeof value === 'undefined' ? '' : value;
  1107.             } else {
  1108.                 return getCookie(key);
  1109.             }
  1110.         }
  1111.        
  1112.         function getSession(key) {
  1113.             key = getWorldKey(key);
  1114.             if (modernizr.localstorage) {
  1115.                 var value = sessionStorage[key];
  1116.                 return typeof value === 'undefined' ? '' : value;
  1117.             } else {
  1118.                 return getCookie(key);
  1119.             }
  1120.         }
  1121.        
  1122.         function get(key) {
  1123.             return getGlobal(key);
  1124.         }
  1125.        
  1126.         function setCookie(key, value, expireMinutes) {
  1127.             key = getWorldKey(key);
  1128.             (function() {
  1129.                 var date_obj = new Date(),
  1130.                     time = date_obj.getTime();
  1131.                 if (typeof expireMinutes === 'undefined') {
  1132.                     time += 60 * 1000 * 24 * 356;
  1133.                 } else {
  1134.                     time += expireMinutes * 1000 * 60;
  1135.                 }
  1136.                 date_obj.setTime(time);
  1137.    
  1138.                 document.cookie = key + "=" + value + ";expires=" + date_obj.toGMTString() + ";";
  1139.             })();
  1140.         }
  1141.        
  1142.         function setGlobal(key, value) {
  1143.             key = getKey(key);
  1144.             if (modernizr.localstorage) {
  1145.                 localStorage[key] = value;
  1146.             } else {
  1147.                 setCookie(key, value);
  1148.             }
  1149.         }
  1150.        
  1151.         function setSession(key, value) {
  1152.             key = getWorldKey(key);
  1153.             if (modernizr.localstorage) {
  1154.                 sessionStorage[key] = value;
  1155.             } else {
  1156.                 setCookie(key, value);
  1157.             }
  1158.         }
  1159.    
  1160.         function set(key, value) {
  1161.             setGlobal(key, value);
  1162.         }
  1163.        
  1164.         function removeSessionItem(key) {
  1165.             key = getKey(key);
  1166.             if (modernizr.localstorage) {
  1167.                 sessionStorage.removeItem(key);
  1168.             }
  1169.             // fuck cookies
  1170.         }
  1171.    
  1172.         function clear() {
  1173.             if (modernizr.localstorage) {
  1174.                 sessionStorage.clear();
  1175.                 localStorage.clear();
  1176.             }
  1177.         }
  1178.        
  1179.         pers.removeSessionItem = removeSessionItem;
  1180.         pers.getWorldKey = getWorldKey;
  1181.         pers.getKey = getKey;
  1182.         pers.set = set;
  1183.         pers.setCookie = setCookie;
  1184.         pers.setGlobal = setGlobal;
  1185.         pers.setSession = setSession;
  1186.         pers.get = get;
  1187.         pers.getCookie = getCookie;
  1188.         pers.getGlobal = getGlobal;
  1189.         pers.getSession = getSession;
  1190.         pers.clear = clear;
  1191.     })(pers || (pers = {}));
  1192.     $.fn.sortElements = (function () {
  1193.         var sort = [].sort;
  1194.         return function (comparator, getSortable) {
  1195.             getSortable = getSortable || function () { return this; };
  1196.             var placements = this.map(function () {
  1197.                 var sortElement = getSortable.call(this),
  1198.                 parentNode = sortElement.parentNode,
  1199.                 // Since the element itself will change position, we have
  1200.                 // to have some way of storing its original position in
  1201.                 // the DOM. The easiest way is to have a 'flag' node:
  1202.                 nextSibling = parentNode.insertBefore(
  1203.                     document.createTextNode(''),
  1204.                     sortElement.nextSibling
  1205.                 );
  1206.    
  1207.                 return function () {
  1208.                     if (parentNode === this) {
  1209.                         throw new Error("You can't sort elements if any one is a descendant of another.");
  1210.                     }
  1211.    
  1212.                     // Insert before flag:
  1213.                     parentNode.insertBefore(this, nextSibling);
  1214.                     // Remove flag:
  1215.                     parentNode.removeChild(nextSibling);
  1216.                 };
  1217.             });
  1218.    
  1219.             return sort.call(this, comparator).each(function (i) {
  1220.                 placements[i].call(getSortable.call(this));
  1221.             });
  1222.         };
  1223.     })();
  1224.    
  1225.     $.fn.outerHTML =
  1226.         function () {
  1227.             return $('<div>').append(this.clone()).remove().html();
  1228.         };
  1229.    
  1230.     /**
  1231.      * Create a dialog box on a fixed position with self closing functionality
  1232.      * @param {string} id The DOM ID of the div
  1233.      * @param content .title: Short title. Defaults to Sangu Package.
  1234.      *                .body: The HTML to show in the body of the tooltip
  1235.      * @param options .top: CSS top in px
  1236.      *                .left: CSS left in px
  1237.      *                .width: CSS width in px. Defaults to 350.
  1238.      *                .showOnce: If true the tooltip will never be shown again once closed. Defaults to true.
  1239.      */
  1240.     function createFixedTooltip(id, content, options) {
  1241.         if (typeof options.width === 'undefined') {
  1242.             options.width = 350;
  1243.         }
  1244.         if (typeof options.showOnce === 'undefined') {
  1245.             options.showOnce = true;
  1246.         }
  1247.         if (typeof content.title === 'undefined') {
  1248.             content.title = "Sangu Package";
  1249.         }
  1250.        
  1251.         var persKey = "fixedToolTip_"+id; // Other implementations depend on this naming
  1252.         if (!options.showOnce || pers.getGlobal(persKey) == '') {
  1253.             content_value.after('<div id="' + id + '" class="vis" style="z-index: 100001; margin: 2px; '
  1254.                 + 'width: '+options.width+'px; display: block; position:absolute; top: '+options.top+'px; left: '+options.left+'px; border: 1px solid black; background-color: #F4E4BC">'
  1255.                 + '<h4>' + '<img class="'+id+'closeTooltip" style="float: right; cursor: pointer;" src="graphic/minus.png">' + content.title + '</h4>'
  1256.                 + '<div style="display: block; text-align: left; margin: 2px;">' + content.body + '</div>'
  1257.                 + '</div>');
  1258.    
  1259.             $("."+id+"closeTooltip", "#" + id).click(function() {
  1260.                 $("#" + id).hide();
  1261.                 if (options.showOnce) {
  1262.                     pers.setGlobal(persKey, "1");
  1263.                 }
  1264.             });
  1265.         }
  1266.     }
  1267.    
  1268.     function createSpoiler(button, content, opened) {
  1269.         return "<div id='spoiler'><input type='button' value='" + button + "' onclick='toggle_spoiler(this)' /><div><span style='display:" + (opened ? 'block' : 'none') + "'>" + content + "</span></div></div>";
  1270.     }
  1271.    
  1272.     function createMoveableWidget(id, title, content) {
  1273.         return '<div id=' + id + '+ class="vis moveable widget"><h4><img style="float: right; cursor: pointer;"'
  1274.                 + ' onclick="return VillageOverview.toggleWidget(\'' + id + '\', this);" src="graphic/minus.png">'
  1275.                 + title + '</h4><div style="display: block;">' + content + '</div></div>';
  1276.     }
  1277.    
  1278.     function printCoord(village, desc) {
  1279.         if (server_settings.coordinateLinkAllowed) {
  1280.             return "<a href=# onclick=\"$('#inputx').val("+village.x+"); $('#inputy').val("+village.y+"); return false;\">" + desc + "</a>";
  1281.         } else {
  1282.             return "<b>" + desc + "</b> <input type=text onclick='this.select(); this.focus()' size=7 value='" + village.x + '|' + village.y + "'>";
  1283.         }
  1284.     }
  1285.  
  1286.     // Activate / deactivate the tool
  1287.     var isSanguActive = pers.get("sanguActive") == "true";
  1288.     if (location.href.indexOf('changeStatus=') > -1) {
  1289.         isSanguActive = location.href.indexOf('changeStatus=true') > -1;
  1290.         pers.set("sanguActive", isSanguActive);
  1291.         pers.setGlobal("fixedToolTip_sanguActivatorTooltip", 1);
  1292.     }
  1293.    
  1294.     var activatorImage = isSanguActive ? "green" : 'red';
  1295.     var activatorTitle = (!isSanguActive ? trans.sp.sp.activatePackage : trans.sp.sp.deactivatePackage) + " (v" + sangu_version + ")";
  1296.    
  1297.     function isSanguCompatible() {
  1298.         return sangu_version.indexOf(game_data.majorVersion) === 0;
  1299.     }
  1300.    
  1301.     // Check for new version
  1302.     var loginMonitor = pers.get("sanguLogin");
  1303.     if (typeof GM_xmlhttpRequest !== "undefined" && !isSanguCompatible() && loginMonitor !== '') {
  1304.         var parts = loginMonitor.match(/(\d+)/g);
  1305.         if (parseInt(parts[2], 10) != (new Date()).getDate()) {
  1306.             GM_xmlhttpRequest({
  1307.                 method: "GET",
  1308.                 url: "http://www.sangu.be/api/sangupackageversion.php",
  1309.                 onload: function (response) {
  1310.                     console.log(response.status, response.responseText.substring (0, 80));
  1311.                 }
  1312.             });
  1313.    
  1314.         }
  1315.     }
  1316.    
  1317.     if (pers.get("forceCompatibility") === '' || pers.get("forceCompatibility") === 'false') {
  1318.         if (isSanguActive) {
  1319.             // Check compatibility with TW version
  1320.             if (!isSanguCompatible()) {
  1321.                 try {
  1322.                     ScriptAPI.register('Sangu Package', sangu_version, 'Laoujin', server_settings.sanguEmail);
  1323.                 } catch (e) {
  1324.                     $("#script_list a[href$='mailto:"+server_settings.sanguEmail+"']").after(" &nbsp;<a href='' id='removeScriptWarning'>"+trans.sp.sp.removeScriptWarning+"</a>");
  1325.                     $("#removeScriptWarning").click(function() {
  1326.                         pers.set("forceCompatibility", "true");
  1327.                     });
  1328.                 }
  1329.             }
  1330.         }
  1331.    
  1332.         // gray icon when tw version doesn't match
  1333.         if (!isSanguCompatible()) {
  1334.             activatorImage = "grey";
  1335.             activatorTitle = trans.sp.sp.activatePackageWithCompatibility.replace("{version}", sangu_version);
  1336.         }
  1337.     }
  1338.    
  1339.     $("#storage").parent()
  1340.         .after(
  1341.             "<td class='icon-box' nowrap><a href=" + location.href.replace("&changeStatus=" + isSanguActive, "")
  1342.             + "&changeStatus=" + (!isSanguActive) + "><img src='graphic/dots/" + activatorImage
  1343.             + ".png' title='" + activatorTitle
  1344.             + "' id='sangu_activator' /></a>&nbsp;</td>");
  1345.    
  1346.     // First time run message - Position beneath resource/storage display
  1347.     if (!isSanguActive) {
  1348.         (function() {
  1349.             var position = $("#storage").position(),
  1350.                 options = {
  1351.                     left: position.left - 150,
  1352.                     top: position.top + 35
  1353.                 },
  1354.                 content = {body: trans.sp.sp.firstTimeRun.replace("{img}", "<img src='graphic/dots/red.png' />")};
  1355.    
  1356.             createFixedTooltip("sanguActivatorTooltip", content, options);
  1357.         }());
  1358.     } else {
  1359.         (function() {
  1360.             var position = $("#storage").position(),
  1361.                 options = {
  1362.                     left: position.left - 150,
  1363.                     top: position.top + 35
  1364.                 },
  1365.                 content = {body: '<b>Sangu stelt zijn nieuwste tool voor: <a href="http://sangu.be">TW Tactics</a>!</b><br><br>Probeer het zeker eens voordat je vijanden het tegen jou beginnen te gebruiken!'};
  1366.    
  1367.             createFixedTooltip("twTacticsTooltip", content, options);
  1368.         }());
  1369.     }
  1370.  
  1371.     // User config
  1372.     // Configure the Sangu Package
  1373.    
  1374.     /*
  1375.     user_data = pers.get('sangusettings');
  1376.     if (user_data !== '') {
  1377.         user_data = JSON.parse(user_data);
  1378.    
  1379.     } else {*/
  1380.         user_data = {
  1381.             proStyle: true,
  1382.             displayDays: false, /* true: display (walking)times in days when > 24 hours. false: always displays in hours */
  1383.             walkingTimeDisplay: "{duration} || {arrival}",
  1384.    
  1385.             colors: {
  1386.                 error: "#FF6347",
  1387.                 good: "#32CD32",
  1388.                 special: "#00FFFF",
  1389.                 neutral: "#DED3B9"
  1390.             },
  1391.    
  1392.             global: {
  1393.                 resources: {
  1394.                     active: true, /* All pages: true/false: color the resources based on how much the storage is filled */
  1395.                     backgroundColors: ['#ADFF2F', '#7FFF00', '#32CD32', '#3CB371', '#228B22', '#FFA500', '#FF7F50', '#FF6347', '#FF4500', '#FF0000'], /* All pages: Colors used for the previous setting. First is least filled, last is most filled storage place */
  1396.                     blinkWhenStorageFull: true /* All pages: Blink the resources if the storage is full */
  1397.                 },
  1398.                 incomings: {
  1399.                     editLinks: true, /* All pages: Edit the incoming attacks/support links: add "show all groups" and "show all pages" to the original links */
  1400.                     track: true,
  1401.                     indicator: "({current} <small>{difference}</small>)",
  1402.                     indicatorTooltip: "Laatste tijdcheck: {elapsed} geleden",
  1403.                     lastTimeCheckWarning: "Aanvallen: {difference}. Laatste tijdcheck: {elapsed} geleden"
  1404.                 },
  1405.                 visualizeFriends: true,
  1406.                 duplicateLogoffLink: false
  1407.             },
  1408.    
  1409.             scriptbar: {
  1410.                 editBoxCols: 700,
  1411.                 editBoxRows: 12
  1412.             },
  1413.    
  1414.             main: {
  1415.                 villageNames: [], /* Add village names to the village headquarters to quickly edit the village name to a preset name. Set to [] or null to disable, for 1 village name use ['MyVillageName'] or for more names: ['name1', 'name2', 'name3'] */
  1416.                 villageNameClick: true, /* true: one of the previous button clicked automatically changes the village name. false: only fills in the name in the textbox but does not click the button */
  1417.                 ajaxLoyalty: true /* Get the loyalty at the building construction/destruction page */
  1418.             },
  1419.    
  1420.             other: {
  1421.                 calculateSnob: true, /* nobles: calculates how many nobles can be produced immediately */
  1422.                 reportPublish: ["own_units", "own_losses", "opp_units", "opp_losses", "carry", "buildings", "own_coords", "opp_coords", "belief"] /* Publishing report: automatically check the 'show' checkboxes */
  1423.             },
  1424.    
  1425.             market: {
  1426.                 resizeImage: true,
  1427.                 autoFocus: true
  1428.             },
  1429.    
  1430.             farmLimit: {
  1431.                 stackColors: ['#DED3B9', '#3CB371', '#FF6347'],
  1432.                 acceptableOverstack: [0.5, 1.2, 1.35], /* Different pages: % of acceptable overstack (only relevant for farmlimit worlds) */
  1433.                 unlimitedStack: [24000, 60000, 100000] /* Different pages: Calculate stacks based on total troops (for non farmlimit worlds) */
  1434.             },
  1435.    
  1436.             command: { /* features for the own troops overview page */
  1437.                 changeTroopsOverviewLink: true, /* Change the link to the own troops overview */
  1438.                 middleMouseClickDeletesRow2: false, /* Let the new default overwrite the old one */
  1439.    
  1440.                 filterMinPopulation: 18000, /* Default number filled in to filter on village stack */
  1441.                 filterMinDefaultType: 'axe', /* This unit type is by default selected in the filter dropdown */
  1442.                 filterMinDefault: 5000, /* The default number filled in to filter on troop amounts */
  1443.                 filterMin: { axe: 7000, spear: 3000, archer: 3000, heavy: 500, catapult: 50, spy: 50, light: 2000, marcher: 2000, ram: 1, catapult: 50, snob: 2 }, /* Default filter numbers for the other units */
  1444.                 filterMinOther: 5000, /* Use this number as the default when the unit is not present in filterMin */
  1445.                 filterAutoSort: true, /* Automatically sort the list on walking distance when entering a target village */
  1446.    
  1447.                 /* These features apply to the commands overview page */
  1448.                 sumRow: true, /* Add a totalrow between different villages */
  1449.                 filterFakeMaxPop: 300, /* Commands fake filter: Everything below 300 pop is considered a fake attack */
  1450.                 bbCodeExport: { /* BB code export */
  1451.                     requiredTroopAmount: 100
  1452.                 }
  1453.             },
  1454.    
  1455.             incomings: {
  1456.                 attackIdDescriptions: [
  1457.                     {minValue: 10, text: "&nbsp;"},
  1458.                     {minValue: 50, text: "10-50"},
  1459.                     {minValue: 100, text: "50-100"},
  1460.                     {minValue: 200, text: "100-200"},
  1461.                     {minValue: 500, text: "200-500"},
  1462.                     {minValue: 1000, text: "500-1000"},
  1463.                     {minValue: 5000, text: "1000-5000"}
  1464.                 ],
  1465.                 attackIdHigherDescription: "5000+"
  1466.             },
  1467.    
  1468.    
  1469.             overviews: {
  1470.                 addFancyImagesToOverviewLinks: true
  1471.             },
  1472.    
  1473.             incoming: { /* Features for the built in TW tagger */
  1474.                 autoOpenTagger: true,       /* Open the tagger automatically if the incoming attack has not yet been renamed */
  1475.                 forceOpenTagger: true,  /* Always open the tagger automatically */
  1476.                 renameInputTexbox: "{unit} ({xy}) {player} F{fields}{night}", /* Possibilities: {id}:internal tw attack id {unit}: short unitname {xy}: coordinates {player} {village}: full village name {c}: continent {fields} distance between the villages {night} indication when attack arrives during the nightbonus. Set to "" to disable. */
  1477.                 villageBoxSize: 600,            /* Adjust the width of the table with the village information (support for 2-click) */
  1478.                 invertSort: true        /* true=noblemen at the top and scouts at the bottom of the table */
  1479.             },
  1480.    
  1481.             overview: { /* The default village overview page */
  1482.                 ajaxSeperateSupport: true, /* Village overview: Seperate own and supported troops */
  1483.                 ajaxSeperateSupportStacks: true, /* Village overview: Calculate stacks for own and supported troops */
  1484.                 canHideDiv: true
  1485.             },
  1486.    
  1487.             mainTagger2: {
  1488.                 active: true,
  1489.                 autoOpen: true,
  1490.                 inputBoxWidth: 300,
  1491.                 defaultDescription: "{xy} OK",
  1492.                 otherDescs:
  1493.                     [
  1494.                         { active: true, name: "Dodgen", hitKey: "D", renameTo: "{xy}----------------------------------------- DODGE THIS" },
  1495.                 { active: true, name: "Nacht", hitKey: "N", renameTo: "{xy} NIGHTBONUS" },
  1496.                 { active: true, name: "Check stack", hitKey: "P", renameTo: "{xy}----------------------------------------- CHECK STACK" },
  1497.                 { active: true, name: "Timen!", hitKey: "T", renameTo: "{xy}***************************************** TIME IT!" },
  1498.                 { active: true, name: "Edelen!", hitKey: "E", renameTo: "{xy}----------------------------------------- NOBLE!!" },
  1499.                 { active: false, name: "Leeg1", hitKey: "L", renameTo: "It has to be, automatically" },
  1500.                 { active: false, name: "Leeg2", hitKey: "U", renameTo: "Check it out, you'd better work it out" },
  1501.                 { active: false, name: "Leeg3", hitKey: "Y", renameTo: "Change to another route" },
  1502.                 { active: false, name: "Leeg4", hitKey: "O", renameTo: "My techniques, strategies, abilities" }
  1503.                     ],
  1504.                 keepReservedWords: true,
  1505.                 reservedWords: [
  1506.                     "Edel.", "Edelman",
  1507.                     "Ram", "Kata.", "Katapult",
  1508.                     "Zcav.", "Zware cavalerie",
  1509.                     "Lcav.", "Lichte Cavalerie", "Bereden boog", "Bboog.",
  1510.                     "Verk.", "Verkenner",
  1511.                     "Bijl", "Zwaard", "Speer", "Boog",
  1512.                     "Ridder"
  1513.                     ],
  1514.                 autoOpenCommands: false,
  1515.                 minutesDisplayDodgeTimeOnMap: 3,
  1516.                 minutesWithoutAttacksDottedLine: 3 * 60,
  1517.                 colorSupport: '#FFF5DA' /* Main village overview: give incoming support a different background color */
  1518.             },
  1519.    
  1520.             villageInfo4: [
  1521.                 {
  1522.                     /* On info_village page add extra link to attack. */
  1523.                     active: true,
  1524.                     off_link: {
  1525.                         name: "Aanvalleuh!",
  1526.                         group: 0,
  1527.                         filter: {
  1528.                             active: true,
  1529.                             unit: "axe",
  1530.                             amount: 5000
  1531.                         },
  1532.                         sort: true,
  1533.                         changeSpeed: "ram",
  1534.                         icon: "graphic/unit/unit_knight.png"
  1535.                     },
  1536.                     def_link: {
  1537.                         name: "Verdedigen!",
  1538.                         group: 0,
  1539.                         filter: {
  1540.                             active: true,
  1541.                             unit: "spear",
  1542.                             amount: 4000
  1543.                         },
  1544.                         sort: true,
  1545.                         changeSpeed: "spear",
  1546.                         icon: "graphic/command/support.png"
  1547.                     }
  1548.                 },
  1549.                 {
  1550.                     /* On info_village page add extra link to attack. */
  1551.                     active: false,
  1552.                     off_link: {
  1553.                         name: "&raquo; off2",
  1554.                         group: 0,
  1555.                         filter: {
  1556.                             active: false,
  1557.                             unit: "axe",
  1558.                             amount: 4000
  1559.                         },
  1560.                         sort: true,
  1561.                         changeSpeed: "ram"
  1562.                     },
  1563.                     def_link: {
  1564.                         name: "&raquo; Snelle Os!",
  1565.                         group: 0,
  1566.                         filter: {
  1567.                             active: true,
  1568.                             unit: "spear",
  1569.                             amount: 1000
  1570.                         },
  1571.                         sort: true,
  1572.                         changeSpeed: "spear"
  1573.                     }
  1574.                 }
  1575.             ],
  1576.    
  1577.             resources: {
  1578.                 requiredResDefault: 250000,
  1579.                 requiredMerchants: 50,
  1580.                 filterMerchants: true,
  1581.                 filterRows: false,
  1582.                 bbcodeMinimumDiff: 50000,
  1583.                 highlightColor: "#FF7F27"
  1584.             },
  1585.    
  1586.             jumper: {
  1587.                 enabled: true,
  1588.                 autoShowInputbox: false
  1589.             },
  1590.    
  1591.             attackAutoRename: {
  1592.                 active: true,
  1593.                 addHaul: false
  1594.             },
  1595.    
  1596.             confirm: {
  1597.                 addExtraOkButton: false,
  1598.                 replaceNightBonus: true,
  1599.                 replaceTribeClaim: true,
  1600.                 addCatapultImages: true
  1601.             },
  1602.    
  1603.             place: {
  1604.                 attackLinks: {
  1605.                     scoutVillage: 100,
  1606.                     scoutPlaceLinks: [5, 100, 500],
  1607.                     scoutPlaceLinksName: "Scout{amount}",
  1608.                    
  1609.                     fakePlaceLink: true,
  1610.                     fakePlaceExcludeTroops: [],
  1611.                     fakePlaceLinkName: "Fake",
  1612.                    
  1613.                     noblePlaceLink: true, /* (de)Activate all noble links */
  1614.                     noblePlaceLinkFirstName: "NobleFirst", /* Name for the first noble which has most troops */
  1615.                    
  1616.                     noblePlaceLinkSupportName: "NobleMin", /* snob with only minimal support */
  1617.                     noblePlaceLinksForceShow: true, /* Show NobleMin also where is only one 1 snob in the village */
  1618.                     nobleSupport: [
  1619.                         { amount: 50, unit: 'light', villageType: 'off' },
  1620.                         { amount: 50, unit: 'heavy', villageType: 'def'}
  1621.                     ],
  1622.                    
  1623.                     noblePlaceLinkDivideName: "NobleDivide",
  1624.                     noblePlaceLinkDivideAddRam: false /* false: Rams are not sent along with NobleDivide */
  1625.                 },
  1626.                 customPlaceLinks:
  1627.                     [
  1628.                         // use minus zero numbers to leave so many units at home
  1629.                         { active: true, type: 'def', name: 'AllDef', spear: 25000, heavy: 5000, archer: 25000, sword: 25000, sendAlong: 0 },
  1630.                         { active: true, type: 'def', name: '1/2-Zc', spear: 4000, heavy: 1000, sendAlong: 500 },
  1631.                         { active: true, type: 'off', name: 'Smart'/*, spear: 25000*/, sword: -10, axe: 25000, spy: 1, light: 5000/*, heavy: 5000*/, marcher: 5000, ram: 5000, catapult: 5000, sendAlong: 0 },
  1632.                         { active: true, type: 'off', name: 'Bijl', spear: 25000, axe: 25000, spy: 1, light: 5000, heavy: 5000, marcher: 5000, sendAlong: 0 },
  1633.                         { active: true, type: 'off', name: 'Zwaard', spear: 25000, sword: -10, axe: 25000, spy: 1, light: 5000, heavy: 5000, marcher: 5000, sendAlong: 0, required: ['sword', 1] },
  1634.    
  1635.                         { active: false, type: 'def', name: 'AlleDef', spear: 25000, sword: 25000, heavy: 5000, archer: 25000, sendAlong: 0 },
  1636.                         { active: false, type: 'def', name: '3deZc', spear: 2500, heavy: 650, sendAlong: 0 },
  1637.                         { active: false, type: 'def', name: '4deZc', spear: 2000, heavy: 500, sendAlong: 0 },
  1638.                         { active: false, type: 'def', name: 'HelftZw', spear: 5000, sword: 5000, sendAlong: 500 },
  1639.                         { active: false, type: 'def', name: '3deZw', spear: 3300, sword: 3300, sendAlong: 0 },
  1640.                         { active: false, type: 'def', name: '4deZw', spear: 2500, sword: 2500, sendAlong: 0 }
  1641.                     ]
  1642.             },
  1643.    
  1644.             /**
  1645.              * units_support_detail: options on the 2 def troop overview pages
  1646.              * on bbcode restack - and others!!)
  1647.              */
  1648.             restack: {
  1649.                 to: 72000,
  1650.                 requiredDifference: 1000,
  1651.                 fieldsDistanceFilterDefault: 30,
  1652.                 filterReverse: true,
  1653.                 autohideWithoutSupportAfterFilter: true,
  1654.                 calculateDefTotalsAfterFilter: true,
  1655.                 defaultPopulationFilterAmount: 80000, /* this isn't related to restack */
  1656.                 removeRowsWithoutSupport: false
  1657.             },
  1658.    
  1659.             showPlayerProfileOnVillage: false,
  1660.             profile: {
  1661.                 show: true,
  1662.                 moveClaim: true,
  1663.                 mapLink: {
  1664.                     show: true,
  1665.                     fill: '#000000',
  1666.                     zoom: '200',
  1667.                     grid: true,
  1668.                     playerColor: '#ffff00',
  1669.                     tribeColor: '#0000FF',
  1670.                     centreX: 500,
  1671.                     centreY: 500,
  1672.                     ownColor: '#FFFFFF',
  1673.                     markedOnly: true,
  1674.                     yourTribeColor: "#FF0000"
  1675.                 },
  1676.                 playerGraph: [["points", false], ["villages", false], ["od", false], ["oda", false], ["odd", false], ["rank", false]], // small / big / false
  1677.                 tribeGraph: [["points", false], ["villages", false], ["od", false], ["oda", false], ["odd", false], ["rank", false], ["members", 'big', true]],
  1678.                 twMapPlayerGraph: { player: [true, true], p_player: [false, false], oda_player: [true, false], odd_player: [true, false], ods_player: [true, false] },
  1679.                 twMapTribeGraph: { tribe: [true, true], p_tribe: [false, false], oda_tribe: [true, false], odd_tribe: [true, false] },
  1680.    
  1681.                 popup: {
  1682.                     show: true,
  1683.                     width: 900,
  1684.                     height: 865,
  1685.                     left: 50,
  1686.                     top: 50
  1687.                 }
  1688.             },
  1689.             smithy:
  1690.                 [
  1691.                     ['offense', { spear: [3, 3], sword: [1, 1], axe: [3, 3], spy: [0, 0], light: [3, 3], heavy: [3, 3], ram: [2, 2], catapult: [0, 0]}],
  1692.                     ['defense', { spear: [3, 3], sword: [1, 1], axe: [0, 3], spy: [0, 3], light: [0, 3], heavy: [3, 3], ram: [0, 1], catapult: [1, 3]}],
  1693.                     ['catapult', { spear: [2, 3], sword: [1, 1], axe: [3, 3], spy: [0, 3], light: [2, 3], heavy: [3, 3], ram: [0, 0], catapult: [2, 3]}]
  1694.                 ],
  1695.             buildings: {
  1696.                 main: [20, 20],
  1697.                 barracks: [25, 25],
  1698.                 stable: [20, 20],
  1699.                 garage: [1, 5],
  1700.                 church: [0, 1],
  1701.                 church_f: [0, 1],
  1702.                 snob: [1, 3],
  1703.                 smith: [20, 20],
  1704.                 place: [1, 1],
  1705.                 statue: [0, 1],
  1706.                 market: [10, 20],
  1707.                 wood: [30, 30],
  1708.                 stone: [30, 30],
  1709.                 iron: [30, 30],
  1710.                 farm: [30, 30],
  1711.                 storage: [30, 30],
  1712.                 hide: [0, 10],
  1713.                 wall: [20, 20]
  1714.             }
  1715.         };
  1716.     //}
  1717.    
  1718.     (function() {
  1719.         var saved_data = pers.get('sangusettings');
  1720.         if (saved_data !== '') {
  1721.             user_data = $.extend(true, user_data, JSON.parse(saved_data));
  1722.         }
  1723.     }());
  1724.  
  1725.     if (isSanguActive) {
  1726.         // world config: global game settings
  1727.         world_config = {
  1728.             hasMilitia: false,
  1729.             nightbonus: {
  1730.                 active: false,
  1731.                 from: 0,
  1732.                 till: 0
  1733.                 },
  1734.             smithyLevels: true,
  1735.             hasChurch: false,
  1736.             hasArchers: false,
  1737.             hasKnight: false,
  1738.             speed: 1,
  1739.             unitSpeed: 1,
  1740.             farmLimit: 0,
  1741.             minFake: 0,
  1742.             hasMinFakeLimit: false,
  1743.             coins: false,
  1744.             maxNobleWalkingTime: 999
  1745.         };
  1746.        
  1747.         if (pers.get('worldconfig') !== '') {
  1748.             world_config = JSON.parse(pers.get("worldconfig"));
  1749.            
  1750.         } else {
  1751.             // load new world through tw API
  1752.             if (server_settings.ajaxAllowed) {
  1753.                 function world_config_setter_unit(configBag, unitInfoXml) {
  1754.                     configBag.hasMilitia = $("config militia", unitInfoXml).length !== 0;
  1755.                 }
  1756.                
  1757.                 function world_config_setter(configBag, infoXml) {
  1758.                     configBag.nightbonus = {
  1759.                         active: $("night active", infoXml).text() === "1",
  1760.                         from: parseInt($("night start_hour", infoXml).text(), 10),
  1761.                         till: parseInt($("night end_hour", infoXml).text(), 10)
  1762.                         };
  1763.                     configBag.smithyLevels = $("game tech", infoXml).text() === "1" || $("game tech", infoXml).text() === "0";
  1764.                     configBag.hasChurch = $("game church", infoXml).text() !== "0";
  1765.                     configBag.hasArchers = $("game archer", infoXml).text() !== "0";
  1766.                     configBag.hasKnight = $("game knight", infoXml).text() !== "0";
  1767.                     configBag.speed = parseFloat($("config speed", infoXml).text());
  1768.                     configBag.unitSpeed = parseFloat($("config unit_speed", infoXml).text());
  1769.                     configBag.farmLimit = parseInt($("game farm_limit", infoXml).text(), 10);
  1770.                     configBag.minFake = parseInt($("game fake_limit", infoXml).text(), 10) / 100;
  1771.                     configBag.hasMinFakeLimit = configBag.minFake > 0;
  1772.                     configBag.coins = $("snob gold", infoXml).text() === "1";
  1773.                     configBag.maxNobleWalkingTime = parseInt($("snob max_dist", infoXml).text(), 10) * configBag.speed * configBag.unitSpeed;
  1774.                 }
  1775.                
  1776.                 function world_config_getter(world) {
  1777.                     // world nl: http://nl16.tribalwars.nl/
  1778.                     // world de: http://de90.die-staemme.de/
  1779.                     if (typeof world === 'undefined') world = '';
  1780.                    
  1781.                     var world_config = {};
  1782.                     $.ajax({
  1783.                         url: world + "interface.php?func=get_unit_info",
  1784.                         async: false,
  1785.                         success: function(xml) {
  1786.                             world_config_setter_unit(world_config, xml);
  1787.                         }
  1788.                     });
  1789.                
  1790.                     $.ajax({
  1791.                         url: world + "interface.php?func=get_config",
  1792.                         async: false,
  1793.                         success: function(xml) {
  1794.                             world_config_setter(world_config, xml);
  1795.                         }
  1796.                     });
  1797.                     return world_config;
  1798.                 }
  1799.                 world_config = world_config_getter();
  1800.                
  1801.             } else {
  1802.                 // Not allowed to get data with ajax: need to store the configuration here
  1803.                 //world_config = (function() {
  1804.                     // paste the world configurations for all worlds on servers that disallow ajax
  1805.                 //})();
  1806.                 alert("No configurations present for this server! (see world_config.js)\nContinueing with default settings.");
  1807.             }
  1808.            
  1809.             pers.set("worldconfig", JSON.stringify(world_config));
  1810.         }
  1811.         // world config
  1812.         // RESOURCES
  1813.         world_data.resources = ['holz', 'lehm', 'eisen'];
  1814.         world_data.resources_en = ['wood', 'stone', 'iron'];
  1815.        
  1816.         // BUILDINGS
  1817.         world_data.buildingsSize =
  1818.             [
  1819.             ["main", [5, 6, 7, 8, 9, 11, 13, 15, 18, 21, 24, 28, 33, 38, 45, 53, 62, 72, 84, 99, 116, 135, 158, 185, 216, 253, 296, 347, 406, 475]],
  1820.             ["barracks", [7, 8, 10, 11, 13, 15, 18, 21, 25, 29, 34, 39, 46, 54, 63, 74, 86, 101, 118, 138, 162, 189, 221, 259, 303]],
  1821.             ["stable", [8, 9, 11, 13, 15, 18, 21, 24, 28, 33, 38, 45, 53, 62, 72, 84, 99, 115, 135, 158]],
  1822.             ["garage", [8, 9, 11, 13, 15, 18, 21, 24, 28, 33, 38, 45, 53, 62, 72]],
  1823.             ["snob", [80, 94, 110]],
  1824.             ["smith", [20, 23, 27, 32, 37, 44, 51, 60, 70, 82, 96, 112, 132, 154, 180, 211, 247, 289, 338, 395]],
  1825.             ["place", [0]],
  1826.             ["market", [20, 23, 27, 32, 37, 44, 51, 60, 70, 82, 96, 112, 132, 154, 180, 211, 247, 289, 338, 395, 462, 541, 633, 740, 866]],
  1827.             ["wood", [5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 21, 24, 28, 33, 38, 43, 50, 58, 67, 77, 89, 103, 119, 138, 159, 183, 212, 245, 283, 326]],
  1828.             ["stone", [10, 11, 13, 15, 17, 19, 22, 25, 29, 33, 37, 42, 48, 55, 63, 71, 81, 93, 106, 121, 137, 157, 179, 204, 232, 265, 302, 344, 392, 447]],
  1829.             ["iron", [10, 12, 14, 16, 19, 22, 26, 30, 35, 41, 48, 56, 66, 77, 90, 105, 123, 144, 169, 197, 231, 270, 316, 370, 433, 507, 593, 696, 811, 949]],
  1830.             ["farm", [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
  1831.             ["storage", [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
  1832.             ["hide", [2, 2, 3, 3, 4, 4, 5, 6, 7, 8]],
  1833.             ["wall", [5, 6, 7, 8, 9, 11, 13, 15, 18, 21, 24, 28, 33, 38, 45, 53, 62, 72, 84, 99]]
  1834.             ];
  1835.        
  1836.         world_data.buildingsPoints =
  1837.             [
  1838.             ["main", [10, 12, 14, 17, 21, 25, 30, 36, 43, 52, 62, 74, 89, 107, 128, 145, 185, 222, 266, 319, 383, 460, 552, 662, 795, 954, 1145, 1648, 1978]],
  1839.             ["barracks", [16, 19, 23, 28, 33, 40, 48, 57, 69, 83, 99, 119, 143, 171, 205, 247, 296, 355, 426, 511, 613, 736, 883, 1060, 1272]],
  1840.             ["stable", [20, 24, 29, 35, 41, 50, 60, 72, 86, 103, 124, 149, 178, 214, 257, 308, 370, 444, 532, 639]],
  1841.             ["garage", [24, 29, 35, 41, 50, 60, 72, 86, 103, 124, 149, 178, 214, 257, 308]],
  1842.             ["snob", [512, 614, 737]],
  1843.             ["smith", [19, 23, 27, 33, 39, 47, 57, 68, 82, 98, 118, 141, 169, 203, 244, 293, 351, 422, 506, 607]],
  1844.             ["place", [0]],
  1845.             ["market", [10, 12, 14, 17, 21, 25, 30, 36, 43, 52, 62, 74, 89, 107, 128, 154, 185, 222, 266, 319, 383, 460, 552, 662, 795]],
  1846.             ["wood", [6, 7, 9, 10, 12, 15, 18, 21, 26, 31, 37, 45, 53, 64, 77, 92, 111, 133, 160, 192, 230, 276, 331, 397, 477, 572, 687, 824, 989, 1187]],
  1847.             ["stone", [6, 7, 9, 10, 12, 15, 18, 21, 26, 31, 37, 45, 53, 64, 77, 92, 111, 133, 160, 192, 230, 276, 331, 397, 477, 572, 687, 824, 989, 1187]],
  1848.             ["iron", [6, 7, 9, 10, 12, 15, 18, 21, 26, 31, 37, 45, 53, 64, 77, 92, 111, 133, 160, 192, 230, 276, 331, 397, 477, 572, 687, 824, 989, 1187]],
  1849.             ["farm", [5, 6, 7, 9, 10, 12, 15, 18, 21, 26, 31, 37, 45, 53, 64, 77, 92, 111, 133, 160, 192, 230, 276, 331, 397, 477, 572, 687, 824, 989]],
  1850.             ["storage", [6, 7, 9, 10, 12, 15, 18, 21, 26, 31, 37, 45, 53, 64, 77, 92, 111, 133, 160, 192, 230, 276, 331, 397, 477, 572, 687, 824, 989, 1187]],
  1851.             ["hide", [5, 6, 7, 9, 10, 12, 15, 18, 21, 26]],
  1852.             ["wall", [8, 10, 12, 14, 17, 20, 24, 29, 34, 41, 50, 59, 71, 86, 103, 123, 148, 177, 213, 256]]
  1853.             ];
  1854.        
  1855.         world_data.buildings = ["main", "barracks", "stable", "garage"];
  1856.         if (world_config.hasChurch) {
  1857.             world_data.buildingsSize.push(["church", [5000, 7750, 12013]]);
  1858.             world_data.buildingsSize.push(["church_f", [5]]);
  1859.             world_data.buildings.push("church");
  1860.         }
  1861.         world_data.buildings = world_data.buildings.concat(["snob", "smith", "place"]);
  1862.         if (world_config.hasKnight) {
  1863.             world_data.buildingsSize.push(["statue", [10]]);
  1864.             world_data.buildingsPoints.push(["statue", [24]]);
  1865.             world_data.buildings.push("statue");
  1866.         }
  1867.         world_data.buildings = world_data.buildings.concat(["market", "wood", "stone", "iron", "farm", "storage", "hide", "wall"]);
  1868.            
  1869.            
  1870.         // UNITS
  1871.         world_data.unitsSize = { "unit_spear": 1, "unit_sword": 1, "unit_axe": 1, "unit_spy": 2, "unit_light": 4, "unit_heavy": 6, "unit_ram": 5, "unit_catapult": 8, "unit_snob": 100 };
  1872.         world_data.unitsSpeed = { "unit_spear": 18, "unit_sword": 22, "unit_axe": 18, "unit_spy": 9, "unit_light": 10, "unit_heavy": 11, "unit_ram": 30, "unit_catapult": 30, "unit_snob": 35, "unit_merchant": 6 };
  1873.        
  1874.         world_data.units_def = ["spear", "sword", "heavy"];
  1875.         world_data.units_off = ["axe", "light", "heavy"];
  1876.         if (!world_config.hasArchers && !world_config.hasKnight) {
  1877.             world_data.unitsPositionSize = [1, 1, 1, 2, 4, 6, 5, 8, 100];
  1878.             world_data.units = ["spear", "sword", "axe", "spy", "light", "heavy", "ram", "catapult", "snob"];
  1879.         } else {
  1880.             world_data.units = ["spear", "sword", "axe"];
  1881.             world_data.unitsPositionSize = [1, 1, 1];
  1882.             if (world_config.hasArchers) {
  1883.                 world_data.units_off.push("marcher");
  1884.                 world_data.units_def.push("archer");
  1885.                 $.extend(world_data.unitsSize, { "unit_archer": 1 }, { "unit_marcher": 5 });
  1886.                 $.extend(world_data.unitsSpeed, { "unit_archer": 18 }, { "unit_marcher": 10 });
  1887.                 world_data.units.push("archer");
  1888.                 world_data.unitsPositionSize.push(1);
  1889.             }
  1890.             world_data.units.push("spy");
  1891.             world_data.unitsPositionSize.push(2);
  1892.             world_data.units.push("light");
  1893.             world_data.unitsPositionSize.push(4);
  1894.             if (world_config.hasArchers) {
  1895.                 world_data.units.push("marcher");
  1896.                 world_data.unitsPositionSize.push(5);
  1897.             }
  1898.             world_data.units.push("heavy");
  1899.             world_data.unitsPositionSize.push(6);
  1900.             world_data.units.push("ram");
  1901.             world_data.unitsPositionSize.push(5);
  1902.             world_data.units.push("catapult");
  1903.             world_data.unitsPositionSize.push(8);
  1904.             if (world_config.hasKnight) {
  1905.                 $.extend(world_data.unitsSize, { "unit_knight": 10 });
  1906.                 $.extend(world_data.unitsSpeed, { "unit_knight": 10 });
  1907.                 world_data.units.push("knight");
  1908.                 world_data.unitsPositionSize.push(10);
  1909.             }
  1910.             world_data.units.push("snob");
  1911.             world_data.unitsPositionSize.push(100);
  1912.         }
  1913.        
  1914.         // Unit speed adjustments
  1915.         world_config.maxNobleWalkingTime *= world_data.unitsSpeed.unit_snob;
  1916.         var speedModifier = world_config.speed * world_config.unitSpeed;
  1917.         if (speedModifier != 1) {
  1918.             $.each(world_data.unitsSpeed, function (index, value) {
  1919.                 world_data.unitsSpeed[index] = world_data.unitsSpeed[index] / speedModifier;
  1920.             });
  1921.         }
  1922.  
  1923.         /**
  1924.          * Adds a . thousands separator
  1925.          */
  1926.         function formatNumber(nStr) {
  1927.             nStr += '';
  1928.             var x = nStr.split('.');
  1929.             var x1 = x[0];
  1930.             var x2 = x.length > 1 ? '.' + x[1] : '';
  1931.             var rgx = /(\d+)(\d{3})/;
  1932.             while (rgx.test(x1)) {
  1933.                 x1 = x1.replace(rgx, '$1' + '.' + '$2');
  1934.             }
  1935.             return x1 + x2;
  1936.         }
  1937.         // DATETIME FUNCTIONS
  1938.         function prettyDate(diff, showSeconds) {
  1939.             diff = diff / 1000;
  1940.             if (diff < 0) {
  1941.                 return "&nbsp;";
  1942.             }
  1943.             if (diff < 60) {
  1944.                 if (showSeconds) {
  1945.                     return diff + " " + trans.sp.tagger.sentSeconds;
  1946.                 }
  1947.                 return trans.sp.tagger.sentNow;
  1948.             }
  1949.             if (diff < 120) {
  1950.                 return trans.sp.tagger.sent1Minute;
  1951.             }
  1952.             if (diff < 3600) {
  1953.                 return Math.floor(diff / 60) + " " + trans.sp.tagger.sentMinutes;
  1954.             }
  1955.             if (diff < 7200) {
  1956.                 return trans.sp.tagger.sent1Hour + ", " + Math.floor((diff - 3600) / 60) + " " + trans.sp.tagger.sentMinutes;
  1957.             }
  1958.             return Math.floor(diff / 3600) + " " + trans.sp.tagger.sentHours + ", " + Math.floor((diff % 3600) / 60) + " " + trans.sp.tagger.sentMinutes;
  1959.         }
  1960.        
  1961.         function twDurationFormat(num) {
  1962.             var days = 0;
  1963.             if (user_data.displayDays) {
  1964.                 days = Math.floor(num / 1440);
  1965.             }
  1966.             num -= days * 1440;
  1967.             var hours = Math.floor(num / 60);
  1968.             num -= hours * 60;
  1969.             var mins = Math.floor(num);
  1970.             num -= mins;
  1971.             var secs = Math.round(num * 60);
  1972.        
  1973.             if (days > 0) {
  1974.                 return days + '.' + pad(hours, 2) + ':' + pad(mins, 2) + ':' + pad(secs, 2);
  1975.             } else {
  1976.                 return pad(hours, 2) + ':' + pad(mins, 2) + ':' + pad(secs, 2);
  1977.             }
  1978.         }
  1979.        
  1980.         function twDateFormat(dat, alwaysPrintFullDate, addYear) {
  1981.             var day = dat.getDate();
  1982.             var cur = new Date().getDate();
  1983.        
  1984.             if (!alwaysPrintFullDate && day == cur) {
  1985.                 return trans.tw.all.today + " " + pad(dat.getHours(), 2) + ':' + pad(dat.getMinutes(), 2) + ':' + pad(dat.getSeconds(), 2);
  1986.             }
  1987.             else if (!alwaysPrintFullDate && day == cur + 1) {
  1988.                 return trans.tw.all.tomorrow + " " + pad(dat.getHours(), 2) + ':' + pad(dat.getMinutes(), 2) + ':' + pad(dat.getSeconds(), 2);
  1989.             }
  1990.             else if (addYear) {
  1991.                 return trans.tw.all.dateOn + " " + dat.getDate() + "." + pad(dat.getMonth() + 1, 2) + "." + (dat.getFullYear() + '').substr(2) + " " + pad(dat.getHours(), 2) + ':' + pad(dat.getMinutes(), 2) + ':' + pad(dat.getSeconds(), 2); // + "(" + dat.getFullYear() + ")";
  1992.             } else {
  1993.                 return trans.tw.all.dateOn + " " + dat.getDate() + "." + pad(dat.getMonth() + 1, 2) + ". " + pad(dat.getHours(), 2) + ':' + pad(dat.getMinutes(), 2) + ':' + pad(dat.getSeconds(), 2); // + "(" + dat.getFullYear() + ")";
  1994.             }
  1995.         }
  1996.        
  1997.         function getTimeFromTW(str) {
  1998.             // NOTE: huh this actually returns the current date
  1999.             // with some new properties with the "str" time
  2000.             //17:51:31
  2001.             var timeParts = str.split(":");
  2002.             var seconds = timeParts[2];
  2003.             var val = {};
  2004.             val.hours = parseInt(timeParts[0], 10);
  2005.             val.minutes = parseInt(timeParts[1], 10);
  2006.             if (seconds.length > 2) {
  2007.                 var temp = seconds.split(".");
  2008.                 val.seconds = parseInt(temp[0], 10);
  2009.                 val.milliseconds = parseInt(temp[1], 10);
  2010.             } else {
  2011.                 val.seconds = parseInt(seconds, 10);
  2012.             }
  2013.             val.totalSecs = val.seconds + val.minutes * 60 + val.hours * 3600;
  2014.             return val;
  2015.         }
  2016.        
  2017.         function getDateFromTW(str, isTimeOnly) {
  2018.             //13.02.11 17:51:31
  2019.             var timeParts, seconds;
  2020.             if (isTimeOnly) {
  2021.                 timeParts = str.split(":");
  2022.                 seconds = timeParts[2];
  2023.                 var val = new Date();
  2024.                 val.setHours(timeParts[0]);
  2025.                 val.setMinutes(timeParts[1]);
  2026.                 if (seconds.length > 2) {
  2027.                     var temp = seconds.split(".");
  2028.                     val.setSeconds(temp[0]);
  2029.                     val.setMilliseconds(temp[1]);
  2030.                 } else {
  2031.                     val.setSeconds(seconds);
  2032.                 }
  2033.                 return val;
  2034.             } else {
  2035.                 var parts = str.split(" ");
  2036.                 var dateParts = parts[0].split(".");
  2037.                 timeParts = parts[1].split(":");
  2038.                 seconds = timeParts[2];
  2039.                 var millis = 0;
  2040.                 if (seconds.length > 2) {
  2041.                     var temp = seconds.split(".");
  2042.                     seconds = temp[0];
  2043.                     millis = temp[1];
  2044.                 } if (dateParts[2].length == 2) {
  2045.                     dateParts[2] = (new Date().getFullYear() + '').substr(0, 2) + dateParts[2];
  2046.                 }
  2047.        
  2048.                 return new Date(dateParts[2], (dateParts[1] - 1), dateParts[0], timeParts[0], timeParts[1], seconds, millis);
  2049.             }
  2050.         }
  2051.        
  2052.         function getDateFromTodayTomorrowTW(str) {
  2053.             var currentT = new Date();
  2054.             var dateParts = [];
  2055.             var parts = $.trim(str).split(" ");
  2056.             if (str.indexOf(trans.tw.all.tomorrow) != -1) {
  2057.                 // morgen om 06:35:29 uur
  2058.                 dateParts[0] = currentT.getDate() + 1;
  2059.                 dateParts[1] = currentT.getMonth();
  2060.             } else if (str.indexOf(trans.tw.all.today) != -1) {
  2061.                 // vandaag om 02:41:40 uur
  2062.                 dateParts[0] = currentT.getDate();
  2063.                 dateParts[1] = currentT.getMonth();
  2064.             } else {
  2065.                 // op 19.05. om 11:31:51 uur
  2066.                 dateParts = parts[1].split(".");
  2067.                 dateParts[1] = parseInt(dateParts[1], 10) - 1;
  2068.             }
  2069.        
  2070.             // last part is "hour" but there is a script from lekensteyn that
  2071.             // corrects the projected arrival time each second
  2072.             // the script has not been updated to add the word "hour" after the time.
  2073.             var timeParts = parts[parts.length - 2].split(":");
  2074.             if (timeParts.length === 1) {
  2075.                 timeParts = parts[parts.length - 1].split(":");
  2076.             }
  2077.        
  2078.             var seconds = timeParts[2];
  2079.             var millis = 0;
  2080.             if (seconds.length > 2) {
  2081.                 var temp = seconds.split(".");
  2082.                 seconds = temp[0];
  2083.                 millis = temp[1];
  2084.             }
  2085.        
  2086.             return new Date(new Date().getFullYear(), dateParts[1], dateParts[0], timeParts[0], timeParts[1], seconds, millis);
  2087.         }
  2088.        
  2089.         function isDateInNightBonus(date) {
  2090.             if (!world_config.nightbonus.active) return false;
  2091.             return date.getHours() >= world_config.nightbonus.from && date.getHours() < world_config.nightbonus.till;
  2092.         }
  2093.         function getBuildingSpace() {
  2094.             var total = 0;
  2095.             for (var building = 0; building < world_data.buildingsSize.length; building++) {
  2096.                 var b = world_data.buildingsSize[building];
  2097.                 if (parseInt(game_data.village.buildings[b[0]], 10) > 0) {
  2098.                     total += b[1][parseInt(game_data.village.buildings[b[0]], 10) - 1];
  2099.                 }
  2100.             }
  2101.             return total;
  2102.         }
  2103.        
  2104.         function getBuildingPoints() {
  2105.             var total = 0;
  2106.             for (var building = 0; building < world_data.buildingsPoints.length; building++) {
  2107.                 var b = world_data.buildingsPoints[building];
  2108.                 if (parseInt(game_data.village.buildings[b[0]], 10) > 0) {
  2109.                     total += b[1][parseInt(game_data.village.buildings[b[0]], 10) - 1];
  2110.                 }
  2111.             }
  2112.             return total;
  2113.         }
  2114.        
  2115.         if (user_data.jumper.enabled) {
  2116.             (function() {
  2117.                 //console.time("jumper");
  2118.                 try {
  2119.                     var cell = "<span style='display: none;' id=sanguJumperFrame>";
  2120.                     cell += "<input type=text type=text size=6 id=sangujumper style='height: 16px; border: 0; top: -2px; position: relative'>";
  2121.                     cell += "</span>";
  2122.                     cell += "&nbsp;<a href=# id=sangujumperOpen><span class='icon ally internal_forum' title='" + trans.sp.jumper.goToMap + "'></span></a>";
  2123.                     $("#menu_row2").append("<td>" + cell + "</td>");
  2124.        
  2125.                     $("#sangujumper").keyup(function (e) {
  2126.                         if (e.which == 13) {
  2127.                             $("#sangujumperOpen").click();
  2128.                         }
  2129.                     });
  2130.        
  2131.                     $("#sangujumperOpen").click(function () {
  2132.                         var input = $("#sangujumper");
  2133.                         if ($("#sanguJumperFrame").is(":visible")) {
  2134.                             var village = getVillageFromCoords(input.val(), true);
  2135.                             if (village.isValid) {
  2136.                                 trackClickEvent("JumperOpen_RealValue");
  2137.                                 // Jump to coordinates on the map
  2138.                                 location.href = getUrlString("&screen=map&x=" + village.x + "&y=" + village.y);
  2139.        
  2140.                             } else {
  2141.                                 // incorrect coordinates
  2142.                                 if (!$("#sangujumperpos").is(":visible")) {
  2143.                                     $("#sangujumperpos").show();
  2144.                                     input.css("border", "1px solid red");
  2145.                                 } else
  2146.                                     $("#sangujumperpos").hide();
  2147.                             }
  2148.                         } else {
  2149.                             // activate mapJumper
  2150.                             var input = $("#sangujumper");
  2151.                             if (input.val() == "") {
  2152.                                 $("#sanguJumperFrame").fadeIn();
  2153.                             } else {
  2154.                                 $("#sanguJumperFrame").show();
  2155.                                 $("#sangujumperOpen").click();
  2156.                             }
  2157.                         }
  2158.        
  2159.                         return false;
  2160.                     });
  2161.        
  2162.                     if (user_data.jumper.autoShowInputbox) {
  2163.                         $("#sangujumperOpen").click();
  2164.                     }
  2165.                 } catch (e) { handleException(e, "jumper"); }
  2166.                 //console.timeEnd("jumper");
  2167.             }());
  2168.         }
  2169.         // Send usage statistics to GA once/day
  2170.         (function() {
  2171.             //console.time("ga");
  2172.             try {
  2173.                 var loginMonitor = pers.get("sanguLogin");
  2174.                 if (loginMonitor !== '') {
  2175.                     var parts = loginMonitor.match(/(\d+)/g);
  2176.                     loginMonitor = new Date(parts[0], parts[1]-1, parts[2]);
  2177.        
  2178.                     //if (Math.abs(loginMonitor.getTime() - (new Date()).getTime()) > 1000 * 3600 * 24) {
  2179.                     if (parseInt(parts[2], 10) != (new Date()).getDate()) {
  2180.                         loginMonitor = '';
  2181.                     }
  2182.                 }
  2183.                 if (loginMonitor === '') {
  2184.                     loginMonitor = new Date();
  2185.                     loginMonitor.setHours(0, 0, 0);
  2186.                     loginMonitor = loginMonitor.getFullYear() + '-' + pad(loginMonitor.getMonth()+1, 2) + '-' +  pad(loginMonitor.getDate(), 2);
  2187.                     trackEvent("ScriptUsage", "DailyUsage", loginMonitor);
  2188.                     pers.set("sanguLogin", loginMonitor);
  2189.        
  2190.                     // also log world/tribe usage
  2191.                     trackEvent("ScriptUsage", "WorldUsage", game_data.world);
  2192.                     trackEvent("ScriptUsage", "TribeUsage", game_data.world + " " + game_data.player.ally_id);
  2193.                     trackEvent("ScriptUsage", "HasPremium", game_data.features.Premium.active ? "Yes" : "No");      // Do we need to support non PA users?
  2194.                     trackEvent("ScriptUsage", "HasAM", game_data.features.AccountManager.active ? "Yes" : "No");    // Do we need to do stuff on the AM pages?
  2195.                 }
  2196.             } catch (e) { handleException(e, "ga"); }
  2197.             //console.timeEnd("ga");
  2198.         }());
  2199.        
  2200.         q("-------------------------------------------------------------------- Start: "+sangu_version);
  2201.  
  2202.         // BEGIN PAGE PROCESSING
  2203.         switch (current_page.screen) {
  2204.             case "overview":
  2205.                 // MAIN VILLAGE OVERVIEW
  2206.                 (function() {
  2207.                     /**
  2208.                      * The slowest unit in the village in the form unit_spear
  2209.                      */
  2210.                     var slowest_unit = null;
  2211.  
  2212.                 (function() {
  2213.                         /**
  2214.                          * Each key is in the form unit_sword and holds the total amount of the type in the village
  2215.                          * (own and supporting troops combined)
  2216.                          */
  2217.                     var totalUnits = {},
  2218.                         /**
  2219.                          * Total population of all units (own + supporting)
  2220.                          */
  2221.                          totalFarm = 0,
  2222.                         /**
  2223.                          * Own population only (total - supporting)
  2224.                          */
  2225.                          ownFarmTotal = 0,
  2226.                         /**
  2227.                          * jQuery div with the troops in the village (becomes the own troops only div)
  2228.                          */
  2229.                          unitTable = $("#show_units"),
  2230.                         /**
  2231.                          * HTML table builder string for the supporting troops div
  2232.                          */
  2233.                          supportingTroopsTable = "<table class=vis width='100%'>";
  2234.                
  2235.                     try {
  2236.                         $("#show_units > h4").prepend(trans.sp.main.unitsReplacement);
  2237.                
  2238.                         // calculate current stack
  2239.                         $("table:first td", unitTable).not(":last").each(function () {
  2240.                             var unit = $('img', this)[0].src,
  2241.                                 unitsSize,
  2242.                                 unitAmount;
  2243.                
  2244.                             unit = unit.substr(unit.lastIndexOf('/') + 1);
  2245.                             unit = unit.substr(0, unit.lastIndexOf('.'));
  2246.                             unitsSize = world_data.unitsSize[unit];
  2247.                             unitAmount = $('strong', this);
  2248.                             unitAmount[0].id = "spAmount" + unit;
  2249.                             unitAmount = unitAmount[0].innerHTML;
  2250.                             if( unit.match("knight") && !unitAmount ) {
  2251.                                 unitAmount = 1;
  2252.                             }
  2253.                             totalUnits[unit] = unitAmount;
  2254.                             totalFarm += unitsSize * unitAmount;
  2255.                
  2256.                             if (slowest_unit == null || world_data.unitsSpeed[slowest_unit] < world_data.unitsSpeed[unit]) {
  2257.                                 slowest_unit = unit;
  2258.                             }
  2259.                         });
  2260.                
  2261.                         // fetch own troops
  2262.                         if (user_data.overview.ajaxSeperateSupport && totalFarm > 0) {
  2263.                             if (server_settings.ajaxAllowed) {
  2264.                                 ajax("place",
  2265.                                     function (placeText) {
  2266.                                         if (placeText.find(".unitsInput").size() > 0) {
  2267.                                             slowest_unit = null;
  2268.                                             placeText.find(".unitsInput").each(function () {
  2269.                                                 // separate own / supporting troops
  2270.                                                 var unit = 'unit_' + this.id.substr(this.id.lastIndexOf("_") + 1);
  2271.                                                 var unitAmount = $(this).next().text().substr(1);
  2272.                                                 unitAmount = parseInt(unitAmount.substr(0, unitAmount.length - 1), 10);
  2273.                                                 var unitsSize = world_data.unitsSize[unit];
  2274.                                                 ownFarmTotal += unitsSize * unitAmount;
  2275.                
  2276.                                                 var unitLabel = $("#spAmount" + unit);
  2277.                                                 var supportingTroopsAmount = totalUnits[unit] - unitAmount;
  2278.                                                 if (supportingTroopsAmount > 0) {
  2279.                                                     var unitDesc = $.trim(unitLabel.parent().text());
  2280.                                                     unitDesc = unitDesc.substr(unitDesc.indexOf(" ") + 1);
  2281.                                                     supportingTroopsTable +=
  2282.                                                         "<tr><td>" + unitLabel.prev().outerHTML()
  2283.                                                             + " <b>" + formatNumber(supportingTroopsAmount)
  2284.                                                             + "</b> "
  2285.                                                             + unitDesc + "</td></tr>";
  2286.                                                 }
  2287.                
  2288.                                                 if (unitAmount > 0) {
  2289.                                                     unitLabel.text(unitAmount);
  2290.                                                     if (slowest_unit == null || world_data.unitsSpeed[slowest_unit] < world_data.unitsSpeed[unit]) {
  2291.                                                         slowest_unit = unit;
  2292.                                                     }
  2293.                                                 } else {
  2294.                                                     unitLabel.parent().parent().hide();
  2295.                                                 }
  2296.                                             });
  2297.                
  2298.                                         } else {
  2299.                                             ownFarmTotal = totalFarm; // No rally point
  2300.                                         }
  2301.                                     }, {async: false});
  2302.                
  2303.                             } else {
  2304.                                 ownFarmTotal = totalFarm; // No ajax
  2305.                             }
  2306.                
  2307.                             if (slowest_unit != null) {
  2308.                                 $("#slowestUnitCell").html("<img title='"+trans.sp.tagger.slowestTip+"' src='graphic/unit/" + slowest_unit + ".png'>").attr("slowestUnit", slowest_unit);
  2309.                             }
  2310.                
  2311.                             if (ownFarmTotal > 0 && user_data.overview.ajaxSeperateSupportStacks) {
  2312.                                 // stack in the village
  2313.                                 unitTable.find("table:first").append("<tr><td><span class='icon header population' title='" + trans.sp.main.ownStackTitle + "'></span>" + stackDisplay(ownFarmTotal).desc + "</td></tr>");
  2314.                             }
  2315.                             if (totalFarm - ownFarmTotal > 0) {
  2316.                                 // stack from other villages
  2317.                                 supportingTroopsTable += "<tr><td><a href='" + getUrlString("screen=place&mode=units") + "'>&raquo; " + trans.sp.main.rallyPointTroops + "</a></td></tr>";
  2318.                                 if (user_data.overview.ajaxSeperateSupportStacks) {
  2319.                                     (function() {
  2320.                                         var supportDisplay = stackDisplay(totalFarm - ownFarmTotal, { showFarmLimit: true });
  2321.                                         supportingTroopsTable +=
  2322.                                             '<tr><td style="border-top: 1px solid #85550d ;background-color: ' + supportDisplay.color + '">'
  2323.                                                 + '<span class="icon header population" title="' + trans.sp.main.supportingStackTitle
  2324.                                                 + '"></span>'
  2325.                                                 + '<b>' + supportDisplay.desc + '</b>' + '</td></tr>';
  2326.                                     }());
  2327.                                 }
  2328.                
  2329.                                 unitTable.after(createMoveableWidget("os_units", trans.sp.main.unitsOther, supportingTroopsTable + "</table>"));
  2330.                             }
  2331.                
  2332.                             // total stack
  2333.                             (function() {
  2334.                                 var cell = $("#order_level_farm"),
  2335.                                     isClassicOverview = cell.length !== 0,
  2336.                                     percentage,
  2337.                                     stackDetails,
  2338.                                     cellContent;
  2339.                
  2340.                                 if (isClassicOverview) {
  2341.                                     cell = cell.parent();
  2342.                                     if (game_data.features.Premium.active) {
  2343.                                         cell = cell.next();
  2344.                                     }
  2345.                                     percentage = world_config.farmLimit == 0 ? "" : cell.children().html();
  2346.                                     stackDisplay(
  2347.                                         totalFarm, {
  2348.                                             showFarmLimit: true,
  2349.                                             percentage: percentage ? percentage.substr(0, percentage.indexOf('%') + 1) : "",
  2350.                                             cell: cell,
  2351.                                             appendToCell: !game_data.features.Premium.active
  2352.                                         });
  2353.                
  2354.                                 } else {
  2355.                                     stackDetails = stackDisplay(
  2356.                                         totalFarm, {
  2357.                                             showFarmLimit: true,
  2358.                                             percentage: $("#l_farm .building_extra").html()
  2359.                                         });
  2360.                
  2361.                                     //cellContent = '<tr><td style="border-top: 1px solid #85550d ;background-color: ' + stackDetails.color + '">' + '<b>' + trans.tw.all.farm + ': ' + stackDetails.desc + '</b>' + '</td></tr>';
  2362.                                     cellContent = ' | <b>' + trans.tw.all.farm + ': ' + stackDetails.desc + '</b>';
  2363.                                     $("#show_units tbody:first td:last").append(cellContent).css("border-top", "1px solid #85550d").css("background-color", stackDetails.color);
  2364.                                 }
  2365.                             }());
  2366.                         }
  2367.                     }
  2368.                     catch (e) {
  2369.                         handleException(e, "supportingunits");
  2370.                     }
  2371.                 }());
  2372.                 // Incoming/outgoing attacks
  2373.                 var mainTable = $("#overviewtable");
  2374.                 var incomingTable = $("#show_incoming_units table.vis:first");
  2375.                 var outgoingTable = $("#show_outgoing_units");
  2376.                 if (incomingTable.size() == 1 || outgoingTable.size() == 1) {
  2377.                     if (incomingTable.size() == 1) {
  2378.                         // tagger - add header
  2379.                         // inputBoxWidth : clicking the button focusses the newly created inputbox
  2380.                         //                 solution is to no longer show inputboxes on screen load
  2381.                         /*if (user_data.mainTagger2.inputBoxWidth != null) {
  2382.                             $("a.rename-icon", incomingTable).click();
  2383.                             $("span.quickedit", incomingTable).each(function() {
  2384.                                 var renameSpan = this;
  2385.                                 //setTimeout(function() {
  2386.                                 $("span.quickedit-edit input:first", renameSpan).width(user_data.mainTagger2.inputBoxWidth);
  2387.                                 //}, 1);
  2388.                             });
  2389.                         }*/
  2390.                
  2391.                         if (user_data.mainTagger2.active && incomingTable.has("img[src*='attack']").size() != 0) {
  2392.                             $("th:first", incomingTable).append("<input type=button value='" + trans.sp.tagger.openButton + "' id=openTaggerButton>");
  2393.                             $("#openTaggerButton").click(function () {
  2394.                                 $(this).hide();
  2395.                
  2396.                                 incomingTable.click(function (e) {
  2397.                                     if (e.target.nodeName === 'IMG') {
  2398.                                         var direction = $(e.target).attr("direction");
  2399.                                         if (direction.length > 0) {
  2400.                                             var rowIndex = parseInt($(e.target).attr("rowIndex"), 10);
  2401.                                             direction = direction == "up";
  2402.                                             $("input.incAt", incomingTable).each( function () {
  2403.                                                 var rowIndexAttributeValue = parseInt($(this).attr("rowIndex"), 10);
  2404.                                                 if ((direction && rowIndexAttributeValue <= rowIndex) || (!direction && rowIndexAttributeValue >= rowIndex)) {
  2405.                                                     $(this).prop("checked", true);
  2406.                                                 } else {
  2407.                                                     $(this).prop("checked", false);
  2408.                                                 }
  2409.                                             });
  2410.                                         }
  2411.                                     }
  2412.                                 });
  2413.                
  2414.                                 var rows = $("tr", incomingTable);
  2415.                                 var dodgeMenu = "<tr><td>";
  2416.                                 dodgeMenu += '<img src="graphic/command/support.png" alt="" id="checkSupport" title="' + trans.sp.tagger.checkAllSupport + '" />';
  2417.                                 dodgeMenu += "&nbsp;";
  2418.                                 dodgeMenu += '<img src="graphic/command/return.png" alt="" id="uncheckSupport" title="' + trans.sp.tagger.uncheckAllSupport + '" />';
  2419.                                 dodgeMenu += "<th colspan=3>";
  2420.                                 dodgeMenu += trans.sp.tagger.renameTo + "<input type=textbox size=30 id=commandInput value='" + user_data.mainTagger2.defaultDescription + "'></th>";
  2421.                                 dodgeMenu += "<th>" + trans.sp.tagger.slowest + "</th>";
  2422.                                 dodgeMenu += "</td>";
  2423.                                 dodgeMenu += "<td colspan=1 id=slowestUnitCell>";
  2424.                                 if (slowest_unit != null) {
  2425.                                     dodgeMenu += "<img title='"+trans.sp.tagger.slowestTip+"' src='graphic/unit/" + slowest_unit + ".png' slowestunit='" + slowest_unit + "'>";
  2426.                                 }
  2427.                                 dodgeMenu += "</td></tr>";
  2428.                                 incomingTable.find("tbody:first").prepend(dodgeMenu);
  2429.                
  2430.                                 // checkbox manipulation
  2431.                                 $("#uncheckSupport").click(function () {
  2432.                                     $("input.incSupport", incomingTable).prop("checked", false);
  2433.                                 });
  2434.                
  2435.                                 $("#checkSupport").click(function () {
  2436.                                     $("input.incSupport", incomingTable).prop("checked", true);
  2437.                                 });
  2438.                
  2439.                                 var buttonParent = $("#commandInput").parent();
  2440.                                 var commandIdToCoordCache = []; // No Ajax call on multiple renames with {xy}
  2441.                                 function renameCommand(commandName) {
  2442.                                     var dodgeCell; // capture last cell for dodgeCell coloring
  2443.                
  2444.                                     function getCommandIdFromDodgeCell(dodgeCell) {
  2445.                                         return Number(dodgeCell.find("span.quickedit").first().attr("data-id"));
  2446.                                     }
  2447.                
  2448.                                     function getVillageCoordsFromCommandId(commandId, callback) {
  2449.                                         if (server_settings.ajaxAllowed) {
  2450.                                             if (commandIdToCoordCache[commandId]) {
  2451.                                                 callback(commandIdToCoordCache[commandId]);
  2452.                
  2453.                                             } else {
  2454.                                                 ajax('screen=info_command&type=other&id='+commandId, function (overview) {
  2455.                                                     var originVillageLink = $(".village_anchor:first", overview).find("a[href]"),
  2456.                                                         originVillageDesc = originVillageLink.html(),
  2457.                                                         originVillage = getVillageFromCoords(originVillageDesc);
  2458.                
  2459.                                                     commandIdToCoordCache[commandId] = originVillage.coord;
  2460.                
  2461.                                                     callback(originVillage.coord);
  2462.                                                 });
  2463.                                             }
  2464.                                         }
  2465.                                         callback('');
  2466.                                     }
  2467.                
  2468.                                     function executeRename(dodgeCell, commandName) {
  2469.                                         function keepTwIcon(dodgeCell, commandName) {
  2470.                                             var oldName = $(".quickedit-label", dodgeCell).text().toUpperCase(),
  2471.                                                 newName = commandName,
  2472.                                                 i,
  2473.                                                 unitName;
  2474.                
  2475.                                             for (i = 0; i < user_data.mainTagger2.reservedWords.length; i++) {
  2476.                                                 unitName = user_data.mainTagger2.reservedWords[i];
  2477.                                                 if (oldName.indexOf(unitName.toUpperCase()) !== -1) {
  2478.                                                     newName = unitName + ' ' + newName;
  2479.                                                     return newName; // Only one icon possible
  2480.                                                 }
  2481.                                             }
  2482.                                             return newName;
  2483.                                         }
  2484.                
  2485.                                         var button = dodgeCell.find("input[type='button']"),
  2486.                                             newName =  user_data.mainTagger2.keepReservedWords ? keepTwIcon(dodgeCell, commandName) : commandName;
  2487.                
  2488.                                         button.prev().val(newName);
  2489.                                         button.click();
  2490.                                     }
  2491.                
  2492.                                     $("input.taggerCheckbox", incomingTable).each(function () {
  2493.                                         var openRenameButton;
  2494.                
  2495.                                         if ($(this).is(":checked")) {
  2496.                                             dodgeCell = $(this).parent().next();
  2497.                
  2498.                                             openRenameButton = $("a.rename-icon", dodgeCell);
  2499.                                             if (openRenameButton.is(":visible")) {
  2500.                                                 openRenameButton.click();
  2501.                                             }
  2502.                
  2503.                                             if (commandName.indexOf("{xy}") !== -1) {
  2504.                                                 getVillageCoordsFromCommandId(getCommandIdFromDodgeCell(dodgeCell), function(vilCoords) {
  2505.                                                     var nameWithCoords = commandName.replace("{xy}", vilCoords);
  2506.                                                     setTimeout(executeRename(dodgeCell, nameWithCoords),200);
  2507.                                                 });
  2508.                
  2509.                                             } else {
  2510.                                                 setTimeout(executeRename(dodgeCell, commandName),200);
  2511.                                             }
  2512.                                         }
  2513.                                     });
  2514.                
  2515.                                     if (dodgeCell != null) {
  2516.                                         var unitSpeed = $("#slowestUnitCell img").attr("slowestunit");
  2517.                                         if (unitSpeed != undefined) {
  2518.                                             dodgeCell = dodgeCell.parent().find("td").last().prev();
  2519.                                             pers.setCookie("sanguDodge" + getQueryStringParam("village"), unitSpeed + "~" + dodgeCell.text(), user_data.mainTagger2.minutesDisplayDodgeTimeOnMap);
  2520.                
  2521.                                             $(".dodgers", incomingTable).css("background-color", "").attr("title", "");
  2522.                                             dodgeCell.css("background-color", user_data.colors.good).attr("title", trans.sp.tagger.activeDodgeTime);
  2523.                                         }
  2524.                                     }
  2525.                                 }
  2526.                
  2527.                                 // std tag button
  2528.                                 var button = $("<input type=button title='" + trans.sp.tagger.renameTooltip + "' value='" + trans.sp.tagger.rename + "' onclick='select();'>");
  2529.                                 button.click(function () {
  2530.                                     trackClickEvent("MainTagger-CustomRename");
  2531.                                     var tagName = $("#commandInput").val();
  2532.                                     renameCommand(tagName);
  2533.                                 });
  2534.                                 buttonParent.append(button);
  2535.                
  2536.                                 if (user_data.mainTagger2.otherDescs != null && user_data.mainTagger2.otherDescs != false) {
  2537.                                     $.ctrl = function(key, callback, args) {
  2538.                                         $(document).keydown(function(e) {
  2539.                                         if(!args) args=[]; // IE barks when args is null
  2540.                                         if(e.keyCode == key.charCodeAt(0) && e.ctrlKey) {
  2541.                                             e.preventDefault();
  2542.                                             e.stopPropagation();
  2543.                                             callback.apply(this, args);
  2544.                                             return false;
  2545.                                         }
  2546.                                         });        
  2547.                                     };
  2548.                                     // custom buttons
  2549.                                     $.each(user_data.mainTagger2.otherDescs, function (index, val) {
  2550.                                         if (val.active) {
  2551.                                         var button = $("<input type=button title='" + trans.sp.tagger.renameButtonShortcutTooltip.replace("{hitkey}", val.hitKey)
  2552.                                             + "' data-rename-to='" + val.renameTo + "' value='" + val.name
  2553.                                             + "' class=\"mainTaggerButtons\">").click(
  2554.                                             function () {
  2555.                                                 // Cannot use input:checked : this works for Firefox but there is a bug in Opera
  2556.                                                 trackClickEvent("MainTagger-ConfigRename");
  2557.                                                 renameCommand($(this).attr("data-rename-to"));
  2558.                                             });
  2559.                            
  2560.                                             buttonParent.append(button);
  2561.                                         }
  2562.                                         $.ctrl(val.hitKey, function(s) {
  2563.                                             trackClickEvent("MainTagger-ConfigRename");
  2564.                                             renameCommand(val.renameTo);
  2565.                                         });
  2566.                                     });
  2567.                                 }
  2568.                
  2569.                                 // add checkboxes
  2570.                                 var lastRowIndex = rows.size(),
  2571.                                     lastSend = 0,
  2572.                                     prevSendTime = 0,
  2573.                                     firstNight = true,
  2574.                                     amountOfAttacks = 0;
  2575.                
  2576.                                 rows.each(function (rowIndex, rowValue) {
  2577.                                     var row = $(rowValue);
  2578.                                     if (rowIndex == 0) {
  2579.                                         // headerrow
  2580.                                         var header = "<td width=1% nowrap>";
  2581.                                         header += "<img src='graphic/command/attack.png' title='" + trans.sp.tagger.checkAllAttacks + "' id=checkAll>&nbsp;<img src='graphic/command/cancel.png' title='" + trans.sp.tagger.uncheckAllAttacks + "' id=uncheckAll>";
  2582.                                         header += "</td>";
  2583.                
  2584.                                         row.replaceWith("<tr>" + header + "<th width='68%'>" + trans.sp.tagger.incomingTroops + "</th><th width='30%'>" + trans.sp.tagger.arrival + "</th><th width='10%'>" + trans.sp.tagger.arrival + "</th><th width=10% nowrap>" + trans.sp.tagger.dodgeTime + "</th><th width='1%'>&nbsp;</th>" + "</tr>");
  2585.                
  2586.                                         $("#checkAll").click(function () {
  2587.                                             $("input.incAt", incomingTable).prop("checked", true);
  2588.                                         });
  2589.                
  2590.                                         $("#uncheckAll").click( function () {
  2591.                                             $("input.incAt", incomingTable).prop("checked", false);
  2592.                                         });
  2593.                                        
  2594.                                     } else {
  2595.                                         // non header row types
  2596.                                         if (row.find("th").size() != 0) {
  2597.                                             // this part is only executed when attacks can be ignored
  2598.                                             // select all checkbox row (right above link rows)
  2599.                                             $("th:first", row).replaceWith("<th><input type=checkbox id=selectAllIgnore> " + $("th:first", row).text() + "</th>");
  2600.                                             $("#selectAllIgnore").click(function () {
  2601.                                                 var ingoreBoxes = $("input[name^='id_']", incomingTable);
  2602.                                                 var isChecked = $("#selectAllIgnore").is(":checked");
  2603.                                                 ingoreBoxes.each(function() {
  2604.                                                     $(this).attr("checked", isChecked);
  2605.                                                 });
  2606.                                             });
  2607.                
  2608.                                             row.prepend("<td title='" + trans.sp.tagger.totalAttacksOnVillage + "' align=center><b># " + amountOfAttacks + "</b></td>").find("td:last").attr("colspan", 4);
  2609.                                            
  2610.                                         } else if (row.find("td").size() == 1) {
  2611.                                             // link-rows (bottom)
  2612.                                             if ($("#switchModus").size() == 0) {
  2613.                                                 if ($("#selectAllIgnore").size() == 0) {
  2614.                                                     // attack hiding disabled in tw settings -> there is not yet a totalrow
  2615.                                                     row.prepend("<td title='" + trans.sp.tagger.totalAttacksOnVillage + "' align=center><b># " + amountOfAttacks + "</b></td>");
  2616.                                                 } else {
  2617.                                                     row.prepend("<td>&nbsp;</td>");
  2618.                                                 }
  2619.                
  2620.                                                 row.before("<tr><td>&nbsp;</td><td colspan=5><a href='' id=switchModus>" + trans.sp.tagger.switchModus + "</a></td></tr>");
  2621.                                                 $("#switchModus").click(function () {
  2622.                                                     trackClickEvent("MainTagger-OpenClose");
  2623.                                                     var editSpans = $("input.incAt", incomingTable).parent().parent().find("span.quickedit"),
  2624.                                                         isInDisplayMode = function (editSpan) {
  2625.                                                             return editSpan.find("span:first").is(":visible");
  2626.                                                         },
  2627.                                                         switchToOpen = isInDisplayMode(editSpans.first());
  2628.                
  2629.                                                     editSpans.each(function() {
  2630.                                                         var editSpan = $(this),
  2631.                                                             isDisplayMode = isInDisplayMode(editSpan);
  2632.                
  2633.                                                         if (switchToOpen && isDisplayMode) {
  2634.                                                             // make input form visible
  2635.                                                             $("a.rename-icon", editSpan).click();
  2636.                                                             //setTimeout(function() {
  2637.                                                             $("span.quickedit-edit input:first", editSpan).width(user_data.mainTagger2.inputBoxWidth);
  2638.                                                             //}, 1);
  2639.                
  2640.                                                         } else if (!switchToOpen && !isDisplayMode) {
  2641.                                                             // make label display visible
  2642.                                                             editSpan.find("span:first").show();
  2643.                                                             editSpan.find("span:last").remove();
  2644.                                                         }
  2645.                                                     });
  2646.                
  2647.                                                     return false;
  2648.                                                 });
  2649.                                             } else {
  2650.                                                 row.prepend("<td>&nbsp;</td>");
  2651.                                             }
  2652.                                             row.find("td:last").attr("colspan", 5);
  2653.                                         } else {
  2654.                                             // normal incoming rows
  2655.                                             var checkboxCell = "<td><input type=checkbox rowIndex=" + rowIndex + " class='taggerCheckbox ";
  2656.                                             var incomingType = $("img[src*='graphic/command/support.png']", this).size() == 1 ? 'incSupport' : "incAt";
  2657.                                             checkboxCell += incomingType + "'";
  2658.                                             if (rowIndex == 1) {
  2659.                                                 checkboxCell += " id=checkFirst";
  2660.                                             }
  2661.                
  2662.                                             var currentArrivalTime = getDateFromTodayTomorrowTW($("td:eq(1)", this).text());
  2663.                                             if (incomingType == 'incAt' && isDateInNightBonus(currentArrivalTime)) {
  2664.                                                 // nightbonus
  2665.                                                 row.find("td:eq(1)").css("background-color", user_data.colors.error);
  2666.                                             }
  2667.                
  2668.                                             // extra column with dodge time
  2669.                                             if (incomingType == 'incAt') {
  2670.                                                 var dodgeTime = getTimeFromTW($("td:eq(2)", this).text());
  2671.                                                 row.find("td:last").before("<td class=dodgers>" + twDurationFormat(dodgeTime.totalSecs / 2 / 60) + "</td>");
  2672.                                                 amountOfAttacks++;
  2673.                                             } else {
  2674.                                                 row.append("<td>&nbsp;</td>");
  2675.                                             }
  2676.                
  2677.                                             // dotted line after x hours no incomings
  2678.                                             if (prevSendTime == 0 || (currentArrivalTime - prevSendTime) / 1000 / 60 > user_data.mainTagger2.minutesWithoutAttacksDottedLine) {
  2679.                                                 if (prevSendTime != 0) {
  2680.                                                     row.find("td").css("border-top", "1px dotted black");
  2681.                                                 }
  2682.                                                
  2683.                                                 prevSendTime = currentArrivalTime;
  2684.                                             }
  2685.                                            
  2686.                                             // black line after each nightbonus
  2687.                                             if (lastSend == 0 || currentArrivalTime > lastSend) {
  2688.                                                 if (lastSend != 0) {
  2689.                                                     row.find("td").css("border-top", "1px solid black");
  2690.                                                     firstNight = false;
  2691.                                                 }
  2692.                
  2693.                                                 lastSend = new Date(currentArrivalTime);
  2694.                                                 if (lastSend.getHours() >= world_config.nightbonus.till) {
  2695.                                                     lastSend.setDate(lastSend.getDate() + 1);
  2696.                                                     lastSend.setHours(world_config.nightbonus.from);
  2697.                                                     lastSend.setMinutes(0);
  2698.                                                     lastSend.setSeconds(0);
  2699.                                                 } else if (lastSend.getHours() < world_config.nightbonus.from) {
  2700.                                                     lastSend.setHours(world_config.nightbonus.from);
  2701.                                                     lastSend.setMinutes(0);
  2702.                                                     lastSend.setSeconds(0);
  2703.                                                 } else {
  2704.                                                     lastSend.setHours(world_config.nightbonus.till);
  2705.                                                     lastSend.setMinutes(0);
  2706.                                                     lastSend.setSeconds(0);
  2707.                                                 }
  2708.                                             }
  2709.                
  2710.                                             // Automatically select?
  2711.                                             if (incomingType == "incAt") {
  2712.                                                 if (firstNight) {
  2713.                                                     var isDefaultDesc = false;
  2714.                                                     if (!isDefaultDesc) {
  2715.                                                         checkboxCell += " checked=true";
  2716.                                                     }
  2717.                                                 }
  2718.                
  2719.                                                 $("span:eq(2)", row).find("input:first").click(function () {
  2720.                                                     $(this).select();
  2721.                                                 });
  2722.                
  2723.                                                 // extra buttons
  2724.                                                 $("td:eq(0)", row).append("<img src='graphic/oben.png' title='" + trans.sp.tagger.allAbove + "' rowIndex=" + rowIndex + " direction='up'> <img src='graphic/unten.png' title='" + trans.sp.tagger.allBelow + "' rowIndex=" + rowIndex + " direction='down'>");
  2725.                                             }
  2726.                
  2727.                                             row.prepend(checkboxCell + "></td>");
  2728.                
  2729.                                             if (user_data.mainTagger2.colorSupport != null && incomingType != "incAt") {
  2730.                                                 row.find("td").css("background-color", user_data.mainTagger2.colorSupport);
  2731.                                             }
  2732.                                         }
  2733.                                     }
  2734.                                 });
  2735.                             });
  2736.                         }
  2737.                     }
  2738.                
  2739.                     // show tagger?
  2740.                     if (user_data.mainTagger2.autoOpen) {
  2741.                         $("#openTaggerButton").click();
  2742.                     }
  2743.                    
  2744.                     // Show attack rename inputboxes
  2745.                     if (user_data.mainTagger2.autoOpenCommands) {
  2746.                         $("#switchModus").click();
  2747.                     }
  2748.                
  2749.                     // BUG: This breaks the TW remembering of the div positions!!
  2750.                     var newLayout = "<tbody><tr><td colspan=2><div class='outerBorder' id=myprettynewcell>";
  2751.                     newLayout += "</div></td></tr></tbody>";
  2752.                     mainTable.append(newLayout);
  2753.                
  2754.                     var prettyCell = $("#myprettynewcell");
  2755.                     prettyCell.append($("#show_incoming_units"));
  2756.                     prettyCell.append($("#show_outgoing_units"));
  2757.                 }
  2758.                 if (user_data.overview.canHideDiv) {
  2759.                     //console.time("main_overview_deletebuttons");
  2760.                
  2761.                     /**
  2762.                      * Array with domIds of the hideable divs
  2763.                      * @type {Array}
  2764.                      */
  2765.                     var currentlyHidden = pers.get("mainvillage_hiddendivs");
  2766.                     currentlyHidden = currentlyHidden ? JSON.parse(currentlyHidden) : [];
  2767.                
  2768.                     // add the X images
  2769.                     $("#leftcolumn,#rightcolumn").find("div.moveable").each(function() {
  2770.                         var self = $(this),
  2771.                             domId = this.id;
  2772.                
  2773.                         if (!self.hasClass("hidden_widget")) {
  2774.                             if (currentlyHidden.indexOf(domId) !== -1) {
  2775.                                 self.hide();
  2776.                             } else {
  2777.                                 var header = self.find("h4:first");
  2778.                                 header.prepend(
  2779.                                     '<span class="sanguHide" '
  2780.                                         + " title='" + trans.sp.main.hideDiv + "'"
  2781.                                         + ' data-divid="' + domId + '"></span>');
  2782.                             }
  2783.                         }
  2784.                     });
  2785.                
  2786.                     // X image event
  2787.                     $(".sanguHide", content_value).click(function() {
  2788.                         var toHideId = $(this).attr("data-divid");
  2789.                         currentlyHidden.push(toHideId);
  2790.                         $("#" + toHideId).hide();
  2791.                         q(currentlyHidden);
  2792.                
  2793.                         pers.set("mainvillage_hiddendivs", JSON.stringify(currentlyHidden));
  2794.                     });
  2795.                
  2796.                     // css for X image
  2797.                     var deleteImage = "data:image/png;base64,"
  2798.                         + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAAXNSR0IArs4c6QAAAARnQU1BAACx"
  2799.                         + "jwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41"
  2800.                         + "LjExR/NCNwAAAT5JREFUOE9jmBqv1R8uD0F9oTI9wZJdAaLI7A5fwTYv3g4fgU4/4VZ3Lgag6OYV"
  2801.                         + "fUd3zj13aAkeBFQAVFZrx8DQGyIN5Nw6t+7p9W14EFABUFm9AwMD0GqgAUChDw/340FABUBljY5M"
  2802.                         + "DN0BYsRrADkJ6Bs0DZe2NL68sfHN3R1XtrfCBSEaKm0YGIAeR9awoj22M1p5z+SIHX0hczI0t/fF"
  2803.                         + "oGtodGJG1vD8+ubqYPXOEMnJMTI9EYqPz69E1oDFDw/PreyPUkw1YiiwYV9Z5fDq1lZkDVj8sKgh"
  2804.                         + "NFiTIcdHPd5RIc2Se/eUZGQNWPwARH1VMU+vbb51akV7UQhcEKEBzQ+4EERDqRUDA9BZxGsotGBg"
  2805.                         + "AMY28UkDpKHJmYX4xAfSAPQH0GXlNlAGEBVbMQAlkNn55gz5QNKCIdOUAQAxTOqhn6cegQAAAABJ"
  2806.                         + "RU5ErkJggg==";
  2807.                
  2808.                     var css = document.createElement("style");
  2809.                     css.type = "text/css";
  2810.                     css.innerHTML = ".sanguHide { margin-left: 2px; float: right; cursor: pointer; "
  2811.                         + "background: url(" + deleteImage + "); width: 16px; height: 16px; }";
  2812.                     document.body.appendChild(css);
  2813.                
  2814.                     // show everything link
  2815.                     content_value.append(
  2816.                         "<br><a href='#' id='resetSanguMenu'>"
  2817.                             + trans.sp.main.showHiddenDivs.replace("{amount}", currentlyHidden.length)
  2818.                             + "</a>"
  2819.                     );
  2820.                
  2821.                     $("#resetSanguMenu").click(function() {
  2822.                         pers.set("mainvillage_hiddendivs", "");
  2823.                         location.reload();
  2824.                     });
  2825.                
  2826.                
  2827.                
  2828.                     //console.timeEnd("main_overview_deletebuttons");
  2829.                 }
  2830.                 }());
  2831.                 break;
  2832.  
  2833.             case "map":
  2834.             (function() {
  2835.                 //console.time("dodge_fromMainTagger");
  2836.                 try {
  2837.                     var isDodge = pers.getCookie("sanguDodge" + getQueryStringParam("village"));
  2838.                     if (isDodge) {
  2839.                         // Display dodge time and slowest unit in the village (cookied from the mainTagger)
  2840.                         isDodge = isDodge.split("~");
  2841.                         var header = $("h2:first", content_value);
  2842.                         $("tbody:first", content_value)
  2843.                             .prepend(
  2844.                                 "<tr><td><table width=100% cellpadding=0 cellspacing=0><tr><td width=99% style='font-size: 18pt'><b>" + header.html()
  2845.                                     + "</b></td><td nowrap width=250><div title='" + trans.sp.map.dodgeLastTagged + "' style='border: 1px solid black; padding: 2px; background-color: "
  2846.                                     + (isDodge[0] == 'unit_snob' ? user_data.colors.special : user_data.colors.good) + "'><img src=graphic/unit/" + isDodge[0] + ".png> <b>" + isDodge[1]
  2847.                                     + "</b></div></td></tr></table></td></tr>");
  2848.                         header.remove();
  2849.                     }
  2850.                 } catch (e) { handleException(e, "dodge_fromMainTagger-"); }
  2851.                 //console.timeEnd("dodge_fromMainTagger");
  2852.             }());
  2853.                 break;
  2854.  
  2855.             case "report":
  2856.                 if (current_page.mode === 'publish') {
  2857.             // Nice to haves when publishing a report
  2858.             (function() {
  2859.                 try {
  2860.                     if (location.href.indexOf('published=1') == -1 && user_data.other.reportPublish != null) {
  2861.                         $.each(user_data.other.reportPublish, function (i, v) { $("#" + v).prop("checked", true); });
  2862.                     } else {
  2863.                         $("h3~p:nth-child(4)").each(function () {
  2864.                             var input = $("h3~p a")[0].href;
  2865.                             if (input.indexOf("?t=") != -1) {
  2866.                                 input = input.substr(0, input.indexOf("?"));
  2867.                             }
  2868.                             $(this).append('<br><input type="text" size="75" onclick="this.select(); this.focus()" value="[url]' + input + '[/url]" />');
  2869.                             input = input.substr(input.lastIndexOf('/') + 1);
  2870.                             $(this).append('<br><input type="text" size="75" onclick="this.select(); this.focus()" value="[report_display]' + input + '[/report_display]" />');
  2871.                             $(this).append('<br><input type="text" size="75" onclick="this.select(); this.focus()" value="[report]' + input + '[/report]" />');
  2872.                         });
  2873.                     }
  2874.                 } catch (e) { handleException(e, "report"); }
  2875.             }());
  2876.                 }
  2877.                 break;
  2878.  
  2879.             case "main":
  2880.             // Alternating row colors
  2881.             $("#buildings,#build_queue").find("tr:odd").addClass("row_b");
  2882.             (function() {
  2883.                 //console.time("main-renamevillage");
  2884.                 try {
  2885.                     if (user_data.main.villageNames != null && user_data.main.villageNames.length > 0) {
  2886.                         var showButtons = true;
  2887.                         $.each(user_data.main.villageNames, function (i, v) { if (game_data.village.name == v) showButtons = false; });
  2888.            
  2889.                         if (showButtons) {
  2890.                             var submitButton = $("input[type='submit']:last");
  2891.                             $.each(user_data.main.villageNames, function (i, v) {
  2892.                                 // rename village to one of the provided villageNames options
  2893.                                 var button = $("<input type=button class='btn' value='" + v + "'>")
  2894.                                     .click(function () {
  2895.                                         trackClickEvent("RenameVillage");
  2896.                                         $("input[name='name']").val(v);
  2897.                                         if (user_data.main.villageNameClick) {
  2898.                                             $("input[type='submit']").click();
  2899.                                         }
  2900.                                     });
  2901.                                 var input = submitButton.parent().append(button);
  2902.                             });
  2903.                         }
  2904.                     }
  2905.                 } catch (e) { handleException(e, "place-"); }
  2906.                 //console.timeEnd("main-renamevillage");
  2907.             }());
  2908.             // show loyalty when building
  2909.             // destroy button is disabled now (but for how long?)
  2910.             if (server_settings.ajaxAllowed && user_data.main.ajaxLoyalty) {
  2911.                 ajax("overview", function (overview) {
  2912.                     var loyalty = $("#show_mood div.vis_item", overview);
  2913.                     if (loyalty.size() == 1) {
  2914.                         $(".modemenu tr:first").append("<td><b>" + trans.tw.main.loyaltyHeader + "</b> " + loyalty.html() + "</td>");
  2915.                     }
  2916.                 });
  2917.             }
  2918.                 break;
  2919.  
  2920.             case "snob":
  2921.                 if (current_page.mode === "train" || current_page.mode === null || current_page.mode === "produce") {
  2922.             if (user_data.other.calculateSnob && !world_config.coins) {
  2923.                 // Calculate for how many nobles/snobs we've got packages
  2924.                 (function() {
  2925.                     try {
  2926.                         //var table = $("table.vis:eq(1)", content_value);
  2927.                         var table = $("table.vis[width='100%']", content_value).first();
  2928.                         var cost = $("td:eq(1)", table).html();
  2929.                         cost = parseInt(cost.substr(0, cost.indexOf(" ")), 10);
  2930.                         var stored = $("tr:eq(1) td:eq(1)", table).html();
  2931.                         stored = parseInt(stored.substr(0, stored.indexOf(" ")), 10);
  2932.                         var canProduce = 0;
  2933.                         while (stored > cost) {
  2934.                             stored -= cost;
  2935.                             cost++;
  2936.                             canProduce++;
  2937.                         }
  2938.            
  2939.                         var sumtable = $("table.main #content_value > table table.vis:last");
  2940.                         assert(sumtable.length, "no snob sumtable");
  2941.                         sumtable.append("<tr><th>" + trans.sp.snob.canProduce
  2942.                             + "</th><td style='border: 1px solid black'><img src='/graphic/unit/unit_snob.png'><b>"
  2943.                             + canProduce + "</b> " +
  2944.                             "+ <img src='graphic/res.png'>"
  2945.                             + stored + "</td></tr>");
  2946.            
  2947.                     } catch (e) { handleException(e, "snob"); }
  2948.                 }());
  2949.             }
  2950.                 }
  2951.                 break;
  2952.  
  2953.             case "info_command":
  2954.             if ($("#running_times").size() > 0) {
  2955.                 // ---------------------------------------INCOMING ATTACK
  2956.                 (function() {
  2957.                     //console.time("info_command-incoming");
  2958.                     try {
  2959.                         var link = $("#contentContainer tr:eq(10) a:last");
  2960.     link.one('click', function () {
  2961.         var infoTable = $("#contentContainer");
  2962.         var table = $("#running_times");
  2963.    
  2964.         // convert runningtime to seconds
  2965.         // TODO: there is a function for this: getTimeFromTW
  2966.         function convertTime(cell) {
  2967.             var time = $(cell).find("td:eq(1)").text();
  2968.             time = time.match(/(\d+):(\d+):(\d+)/);
  2969.    
  2970.             var obj = {};
  2971.             obj.hours = parseInt(time[1], 10);
  2972.             obj.minutes = parseInt(time[2], 10);
  2973.             obj.seconds = parseInt(time[3], 10);
  2974.             obj.totalSeconds = obj.hours * 3600 + obj.minutes * 60 + obj.seconds;
  2975.    
  2976.             return obj;
  2977.         }
  2978.    
  2979.         // Sort on runningtime
  2980.         var unitRows = $("tr:gt(1)", table);
  2981.         unitRows.sortElements(function (a, b) {
  2982.                 return convertTime(a).totalSeconds > convertTime(b).totalSeconds ? 1 : -1;
  2983.             });
  2984.    
  2985.         // header sent times
  2986.         $("th:first", table).attr("colspan", 5);
  2987.         $("th:eq(2)", table).after("<th>" + trans.sp.tagger.sentOn + "</th><th>" + trans.sp.tagger.ago + "</th>");
  2988.    
  2989.         var infoCell = $("td", infoTable);
  2990.         var attacker = infoCell.eq(5).text();
  2991.         var attackerVillageName = infoCell.eq(7).text();
  2992.         var attackerVillage = getVillageFromCoords(attackerVillageName);
  2993.         var defender = infoCell.eq(10).text();
  2994.         var defenderVillage = getVillageFromCoords(infoCell.eq(12).text());
  2995.         var arrivalTime = getDateFromTW(infoCell.eq(14).text());
  2996.         var fields = parseInt(getDistance(attackerVillage.x, defenderVillage.x, attackerVillage.y, defenderVillage.y).fields, 10);
  2997.    
  2998.         var isNightbonus = isDateInNightBonus(arrivalTime);
  2999.         if (isNightbonus) {
  3000.             infoCell.eq(14).css("background-color", user_data.colors.error);
  3001.         }
  3002.    
  3003.         var remainingRunningTime = convertTime($("tr:eq(9)", infoTable)),
  3004.             plausibleSpeedFound = false;
  3005.    
  3006.         unitRows.each(function () {
  3007.             var unit = $("img:first", this).attr("src");
  3008.             unit = unit.substr(unit.lastIndexOf("unit_") + 5);
  3009.             unit = unit.substr(0, unit.indexOf("."));
  3010.    
  3011.             if (unit == "spear") {
  3012.                 $(this).hide();
  3013.             } else {
  3014.                 var runningTime = convertTime(this),
  3015.                     newDate = new Date(arrivalTime.getTime() - runningTime.totalSeconds * 1000),
  3016.                     sendAt = prettyDate((new Date()).getTime() - newDate.getTime()),
  3017.                     troopsRow = this,
  3018.                     str;
  3019.    
  3020.                 // Extra column with time sent
  3021.                 $("td:eq(2)", troopsRow).before("<td>" + twDateFormat(newDate, true) + "</td><td>" + sendAt + "</td>");
  3022.    
  3023.                 // Rename default command name for each speed
  3024.                 if (user_data.incoming.renameInputTexbox) {
  3025.                     str = user_data.incoming.renameInputTexbox;
  3026.    
  3027.                     var attackId = $("span.quickedit", this).attr("data-id");
  3028.                     str = str.replace("{village}", attackerVillageName).replace("{c}", attackerVillage.continent()).replace("{id}", attackId);
  3029.                     str = str.replace("{player}", attacker).replace("{xy}", attackerVillage.coord).replace("{unit}", trans.tw.units.twShortNames[unit]);
  3030.                     str = str.replace("{fields}", fields);
  3031.                     if (str.indexOf("{night}") != -1) {
  3032.                         if (isNightbonus) {
  3033.                             str = str.replace("{night}", trans.sp.tagger.arrivesInNightBonus);
  3034.                         } else {
  3035.                             str = str.replace("{night}", "");
  3036.                         }
  3037.                     }
  3038.    
  3039.                     if (false) {
  3040.                         // don't show rename inputs, just the labels
  3041.                         $("span.quickedit-label", troopsRow).text(str).show();
  3042.                     } else {
  3043.                         // show the rename inputboxes on form load
  3044.                         $("span.quickedit-label", troopsRow).text(str);
  3045.                         setTimeout(function() {
  3046.                             $("a.rename-icon", troopsRow).click();
  3047.                             $("#focusPlaceHolder").focus();
  3048.                         }, 1);
  3049.                     }
  3050.    
  3051.                     if (runningTime.totalSeconds > remainingRunningTime.totalSeconds && !plausibleSpeedFound) {
  3052.                         plausibleSpeedFound = true;
  3053.    
  3054.                         $("table:first", content_value).prepend(
  3055.                             "<input type=submit class='btn' id=focusPlaceHolder value='"
  3056.                                 + trans.sp.tagger.tagIt
  3057.                                 + " (" + trans.tw.units.twShortNames[unit] + ")'"
  3058.                                 + " data-command-name='" + str + "'"
  3059.                                 + ">");
  3060.    
  3061.                         $("#focusPlaceHolder").click(function () {
  3062.                             trackClickEvent("TagDefault");
  3063.                             $("#command_comment").text($(this).attr("data-command-name"));
  3064.                             $("#command_comment+a").click();
  3065.                             setTimeout(function() {
  3066.                                 $("#quickedit-rename input:last").click();
  3067.                             }, 1);
  3068.                             $(this).val(trans.sp.tagger.tagged).attr("disabled", "disabled");
  3069.                         });
  3070.    
  3071.                         if (unit == "snob") {
  3072.                             $("tr:last td", table).css("background-color", user_data.colors.error);
  3073.                         }
  3074.                     }
  3075.                 }
  3076.    
  3077.                 // Possible send times (now) in bold
  3078.                 if (runningTime.totalSeconds > remainingRunningTime.totalSeconds) {
  3079.                     $(this).css("font-weight", "bold");
  3080.                 }
  3081.             }
  3082.         });
  3083.    
  3084.         // nobles can only walk so far
  3085.         var nobles = $("tr:last", table);
  3086.         if (convertTime(nobles).totalSeconds / 60 > world_config.maxNobleWalkingTime) {
  3087.             nobles.find("td").css("text-decoration", "line-through");
  3088.         }
  3089.    
  3090.         if (user_data.incoming.invertSort) {
  3091.             unitRows.sortElements(function (a, b) {
  3092.                 return convertTime(a).totalSeconds < convertTime(b).totalSeconds ? 1 : -1;
  3093.             });
  3094.         }
  3095.     });
  3096.            
  3097.                         // AUTO OPEN TAGGER
  3098.                         if (user_data.incoming.forceOpenTagger || (user_data.incoming.autoOpenTagger && $("#labelText").text() == trans.tw.incoming.defaultCommandName)) {
  3099.                             link.click();
  3100.                         }
  3101.            
  3102.                         if (user_data.proStyle && user_data.incoming.villageBoxSize != null && user_data.incoming.villageBoxSize != false) {
  3103.                             $("table:first", content_value).css("width", user_data.incoming.villageBoxSize);
  3104.                         }
  3105.            
  3106.                     } catch (e) { handleException(e, "info_command-incoming"); }
  3107.                     //console.timeEnd("info_command-incoming");
  3108.                 }());
  3109.                
  3110.             } else {
  3111.                 (function() {
  3112.                     //console.time("info_command-command");
  3113.                     try {
  3114.                         // Own attack/support/return ---------------------------------------------------------------------------------- Own attack/support/return
  3115.                         var infoTable = $("table.vis:first", content_value);
  3116.                         var type = $("h2:first", content_value).text();
  3117.                         var catapultTargetActive = infoTable.find("tr:eq(5) td:eq(0)").text() == trans.tw.command.catapultTarget;
  3118.            
  3119.                         infoTable.width(600);
  3120.            
  3121.                         // Add troop returntime and annulation return time
  3122.                         var isSupport = type.indexOf(trans.tw.command.support) == 0;
  3123.                         var offset = 5;
  3124.                         if (catapultTargetActive) {
  3125.                             offset += 1;
  3126.                         }
  3127.                         var arrivalCell = infoTable.find("tr:eq(" + (offset + 1) + ") td:last");
  3128.            
  3129.                         if (type.indexOf(trans.tw.command.returnText) == -1
  3130.                             && type.indexOf(trans.tw.command.abortedOperation) == -1) {
  3131.            
  3132.                             var duration = getTimeFromTW(infoTable.find("tr:eq(" + offset + ") td:last").text());
  3133.                             var imgType = !isSupport ? "attack" : "support";
  3134.                             arrivalCell.prepend("<img src='graphic/command/" + imgType + ".png' title='" + trans.sp.command.arrival + "'>&nbsp; " + trans.tw.all.dateOn + " ").css("font-weight", "bold");
  3135.                             var stillToRun = getTimeFromTW(infoTable.find("tr:eq(" + (offset + 2) + ") td:last").text());
  3136.            
  3137.                             var cancelCell = infoTable.find("tr:last").prev();
  3138.                             var canStillCancel = cancelCell.has("a").length;
  3139.                             if (canStillCancel) {
  3140.                                 cancelCell.find("td:first").attr("colspan", "1").attr("nowrap", "nowrap");
  3141.                                 var returnTime = getDateFromTW($("#serverTime").text(), true);
  3142.                                 returnTime = new Date(returnTime.valueOf() + (duration.totalSecs - stillToRun.totalSecs) * 1000);
  3143.                                 cancelCell.append("<td>" + trans.sp.command.returnOn + "</td><td id=returnTimer>" + twDateFormat(returnTime, true, true).substr(3) + "</td>");
  3144.            
  3145.                                 setInterval(function timeCounter() {
  3146.                                     var timer = $("#returnTimer");
  3147.                                     var newTime = new Date(getDateFromTW(timer.text()).valueOf() + 2000);
  3148.                                     timer.text(twDateFormat(newTime, true, true).substr(3));
  3149.                                 }, 1000);
  3150.            
  3151.                                 cancelCell = cancelCell.prev();
  3152.                             }
  3153.            
  3154.                             if (type.indexOf(trans.tw.command.attack) == 0) {
  3155.                                 var returnTimeCell = cancelCell.find("td:last");
  3156.                                 returnTimeCell.html("<img src='graphic/command/return.png' title='" + cancelCell.find("td:first").text() + "'>&nbsp; <b>" + returnTimeCell.text() + "</b>");
  3157.                             }
  3158.                         } else {
  3159.                             var imgType = type.indexOf(trans.tw.command.abortedOperation) == 0 ? imgType = "cancel" : "return";
  3160.                             arrivalCell.prepend("<img src='graphic/command/" + imgType + ".png' title='" + trans.sp.command.arrival + "'>&nbsp; " + trans.tw.all.dateOn + " ").css("font-weight", "bold");
  3161.                         }
  3162.            
  3163.                         var player = infoTable.find("td:eq(7) a").text();
  3164.                         var village = getVillageFromCoords(infoTable.find("td:eq(9) a").text());
  3165.                         var second = infoTable.find("td:eq(" + (13 + (catapultTargetActive ? 2 : 0)) + ")").text();
  3166.                         var haulDescription = "";
  3167.            
  3168.                         if (type.indexOf(trans.tw.command.returnText) == 0) {
  3169.                             infoTable = $("> table.vis:last", content_value);
  3170.                             if (infoTable.find("td:first").text() == trans.tw.command.haul) {
  3171.                                 haulDescription = infoTable.find("td:last").text().match(/\s(\d+)\/(\d+)$/);
  3172.                                 if (haulDescription) {
  3173.                                     haulDescription = formatNumber(haulDescription[1]) + " / " + formatNumber(haulDescription[2]);
  3174.                                 } else {
  3175.                                     assert(infoTable.find("td:last").text() + " didn't match regexp for tw.command.haul");
  3176.                                 }
  3177.                                 infoTable = infoTable.prev();
  3178.                             }
  3179.                             infoTable = infoTable.find("tr:last");
  3180.                         } else {
  3181.                             infoTable = $("> table.vis:last", content_value);
  3182.                         }
  3183.            
  3184.                         var unitsSent = {};
  3185.                         $.each(world_data.units, function (i, val) {
  3186.                             unitsSent[val] = parseInt($("td:eq(" + i + ")", infoTable).text(), 10);
  3187.                         });
  3188.                         var unitsCalc = calcTroops(unitsSent);
  3189.                         unitsCalc.colorIfNotRightAttackType($("h2:first", content_value), !isSupport);
  3190.            
  3191.                         if (user_data.attackAutoRename.active) {
  3192.                             $.each($('.quickedit'), function(){
  3193.                                 var renamed = buildAttackString(village.coord, unitsSent, player, isSupport, 0, haulDescription),
  3194.                                     commandID = $(this).attr('data-id'),
  3195.                                     $this = $(this);
  3196.            
  3197.                                 if (server_settings.ajaxAllowed) {
  3198.                                     $.ajax({
  3199.                                        url: game_data.link_base_pure+'info_command&ajaxaction=edit_other_comment&id='+commandID+'&h='+game_data.csrf+'&',
  3200.                                        method: 'post',
  3201.                                        data: { text: renamed },
  3202.                                        success:function() {
  3203.                                            $this.find(".quickedit-label:first").text(renamed);
  3204.                                        }
  3205.                                     });
  3206.                                 }
  3207.                             });
  3208.                         }
  3209.            
  3210.                         /*if (server_settings.ajaxAllowed) {
  3211.                          ajax("overview", function(overviewtext) {
  3212.                          var idnumberlist = [];
  3213.                          var index = 0;
  3214.                          var links = $(overviewtext).find("#show_outgoing_units").find("table").find("td:first-child").find("a:first-child").find("span");
  3215.                          //^enkel 'find codes, dus alles wegselecteren wat onnodig is.
  3216.            
  3217.                          links.each(function(){
  3218.                          var idgetal = $(this).attr('id').match(/\d+/);
  3219.                          idnumberlist[index]=idgetal[0];
  3220.                          index++;
  3221.                          $.trim(idnumberlist[index]);
  3222.                          });
  3223.            
  3224.                          idthisattack= location.href.match(/id=(\d+)/);// deze aanval ophalen
  3225.                          var idthisattacktrim = $.trim(idthisattack[1]); //eerste callback: Datgeen tussen haakjes dus. En gelijk maar trimmen, voor het geval dat.
  3226.                          var counter=$.inArray(idthisattacktrim, idnumberlist);
  3227.                          var arraylength = idnumberlist.length;
  3228.                          var arraylengthminusone = arraylength -1;
  3229.                          if (counter != arraylengthminusone) {
  3230.                          var nextcommandID = idnumberlist[(counter +1)];}
  3231.                          if (counter != 0) {
  3232.                          var lastcommandID = idnumberlist[(counter - 1)];
  3233.                          }
  3234.                          villageid = location.href.match(/village=(\d+)/);
  3235.                          //alert(villageid[1]);
  3236.                          if (counter != 0) {
  3237.                          content_value.find("h2").after('<table><tr><td id="lastattack" style="width:83%"><a href="/game.php?village=' + villageid + '&id=' + lastcommandID + '&type=own&screen=info_command">'+ trans.sp.command.precedingAttack + '</a></td> </tr> </table>');
  3238.                          }
  3239.                          else {
  3240.                          content_value.find("h2").after('<table><tr><td id="lastattack" style="width:83%"><b> XX</b></td> </tr> </table>');
  3241.                          }
  3242.                          if (counter != arraylengthminusone){
  3243.                          $("#lastattack").after('<td id="nextcommand" ><a href="/game.php?village=' + villageid + '&id=' + nextcommandID + '&type=own&screen=info_command">'+ trans.sp.command.nextAttack+ '</a></td>');
  3244.                          }
  3245.                          else {
  3246.                          $("#lastattack").after('<td id="nextcommand"><b>XX</b></td>');
  3247.                          }
  3248.            
  3249.                          //alert("Hoi");
  3250.                          }, {});
  3251.                          }*/
  3252.            
  3253.                         // When sending os, calculate how much population in total is sent
  3254.                         if (isSupport) {
  3255.                             var totalPop = 0;
  3256.                             $.each(world_data.units, function (i, val) {
  3257.                                 var amount = unitsSent[val];
  3258.                                 if (amount != 0) {
  3259.                                     totalPop += amount * world_data.unitsPositionSize[i];
  3260.                                 }
  3261.                             });
  3262.            
  3263.                             var unitTable = $("table.vis:last", content_value);
  3264.                             unitTable.find("tr:first").append('<th width="50"><span class="icon header population" title="' + trans.sp.all.population + '"></span></th>');
  3265.                             unitTable.find("tr:last").append('<td>' + formatNumber(totalPop) + '</td>');
  3266.                         }
  3267.                     } catch (e) { handleException(e, "info_command-command"); }
  3268.                     //console.timeEnd("info_command-command");
  3269.                 }());
  3270.             }
  3271.                 break;
  3272.  
  3273.             case "market":
  3274.             (function() {
  3275.                 try {
  3276.                     if (location.href.indexOf('try=confirm_send') > -1) {
  3277.                         if (user_data.proStyle && user_data.market.autoFocus) {
  3278.                             $("input[type='submit']").focus();
  3279.                         }
  3280.                     }
  3281.                     else if (location.href.indexOf('&mode=') == -1 || location.href.indexOf('&mode=send') > -1) {
  3282.                         if (location.href.indexOf('try=confirm_send') == -1) {
  3283.                             // Spice up market:
  3284.                             // 120 x 106 pixels: There are market images that are smaller
  3285.                             // Making all images equally large results in the OK button remaining on the same place
  3286.                             if (user_data.proStyle && user_data.market.resizeImage) {
  3287.                                 $("img[src*='big_buildings/market']").width(120).height(106);
  3288.                             }
  3289.            
  3290.                             // New last village:
  3291.                             $("input[type='submit']").click(function () {
  3292.                                 var village = getVillageFromCoords($("#inputx").val() + "|" + $("#inputy").val());
  3293.                                 if (village.isValid) {
  3294.                                     pers.set("lastVil", village.coord);
  3295.                                 }
  3296.                             });
  3297.            
  3298.                             // Add last & target
  3299.                             var vilHome = getVillageFromCoords(game_data.village.coord);
  3300.            
  3301.                             var targetLocation = $("#inputx").parent().parent().parent();
  3302.                             var cookie = pers.get("lastVil");
  3303.                             var coord = getVillageFromCoords(cookie);
  3304.                             var htmlStr = '';
  3305.                             if (coord.isValid) {
  3306.                                 var dist = getDistance(coord.x, vilHome.x, coord.y, vilHome.y, 'merchant');
  3307.                                 htmlStr = printCoord(coord, "&raquo; " + trans.sp.all.last + ": " + coord.x + "|" + coord.y);
  3308.                                 htmlStr += "&nbsp; <span id=lastVilTime>" + dist.html + "</span>";
  3309.                             }
  3310.            
  3311.                             // Add target village
  3312.                             var target = getVillageFromCoords(spTargetVillageCookie());
  3313.                             if (target.isValid) {
  3314.                                 var dist = getDistance(target.x, vilHome.x, target.y, vilHome.y, 'merchant');
  3315.                                 if (htmlStr.length > 0) {
  3316.                                     htmlStr += "<br>";
  3317.                                 }
  3318.                                 htmlStr += printCoord(target, "&raquo; " + trans.sp.all.target + ": " + target.x + "|" + target.y) + " &nbsp;<span id=targetVilTime>" + dist.html + "</span>";
  3319.                             }
  3320.            
  3321.                             if (htmlStr.length > 0) {
  3322.                                 targetLocation.append("<tr><td colspan=2>" + htmlStr + "</td></tr>");
  3323.                             }
  3324.            
  3325.                             // Calculate total resources sent
  3326.                             var table = $("table.vis:last");
  3327.                             if (table.prev().text() == trans.tw.market.incomingTransports) {
  3328.                                 var sent = { stone: 0, wood: 0, iron: 0 };
  3329.                                 table.find("tr:gt(0)").each(function () {
  3330.                                     var cell = $(this).find("td:eq(1)");
  3331.                                     var resources = $.trim(cell.text().replace(/\./g, "").replace(/\s+/g, " ")).split(" ");
  3332.            
  3333.                                     for (var i = 0; i < resources.length; i++) {
  3334.                                         if (resources[i]) {
  3335.                                             var restype = cell.find("span.icon:eq(" + i + ")");
  3336.                                             for (var resIndex = 0; resIndex < world_data.resources_en.length; resIndex++) {
  3337.                                                 if (restype.hasClass(world_data.resources_en[resIndex])) {
  3338.                                                     sent[world_data.resources_en[resIndex]] += parseInt(resources[i], 10);
  3339.                                                 }
  3340.                                             }
  3341.                                         }
  3342.                                     }
  3343.                                 });
  3344.            
  3345.                                 table.append("<tr><th>" + trans.sp.all.total + ":</th><td colspan=3><img src=graphic/holz.png> " + formatNumber(sent.wood) + "&nbsp; <img src=graphic/lehm.png> " + formatNumber(sent.stone) + "&nbsp; <img src=graphic/eisen.png> " + formatNumber(sent.iron) + "</td></tr>");
  3346.                             }
  3347.                         }
  3348.                     }
  3349.                 } catch (e) { handleException(e, "market"); }
  3350.             }());
  3351.                 break;
  3352.  
  3353.             case "settings":
  3354.                 // Add sangu to the menu
  3355.             (function() {
  3356.                 var settingsMenu = $("table.vis:first", content_value);
  3357.                 settingsMenu.append(
  3358.                     "<tr><td>&nbsp;</td></tr><tr><th><a href='"
  3359.                         + getUrlString("screen=settings&mode=sangu")
  3360.                         + "'>Sangu Package</a></th></tr>");
  3361.            
  3362.                 settingsMenu.append(
  3363.                     "<tr id='sanguImportRow'><td>"
  3364.                         + "<a href='#' id='sanguImport'>" + trans.sp.sp.settings.importSettings + "</a>"
  3365.                         + "</td></tr>"
  3366.                         + "<tr id='sanguExportRow'><td>"
  3367.                         + "<a href='#' id='sanguExport'>" + trans.sp.sp.settings.exportSettings + "</a>"
  3368.                         + "</td></tr>");
  3369.            
  3370.                 settingsMenu.after(
  3371.                     "<br>"
  3372.                         + "<a target='_blank' href='http://"+server_settings.sangu+"'>"
  3373.                         + "<div style='width: 100%; text-align: center; border: 1px solid black; background-color: yellow; padding: 10px 0 10px;'>"
  3374.                         + "Sangu Website"
  3375.                         + "</div>"
  3376.                         + "</a>");
  3377.            
  3378.                 /**
  3379.                  * Creates the textarea (and other UI elements) for displaying the user_data
  3380.                  * @param activate {string} either Import or Export
  3381.                  * @param deactivate {string} either Export or Import
  3382.                  * @param textAreaValue {string} the content to assign to the textarea
  3383.                  */
  3384.                 function importExportHandler(activate, deactivate, textAreaValue) {
  3385.                     var settingsContainer = $("#sanguSettingsTextArea");
  3386.                     $("#sangu"+activate+"Row").addClass("row_b");
  3387.                     $("#sangu"+deactivate+"Row").hide();
  3388.            
  3389.                     $("#sanguSettingsForm").hide();
  3390.                     if (settingsContainer.length === 0) {
  3391.                         //sanguConfigTitle is the h3 defined in inject.js
  3392.                         $("#sanguConfigTitle").parent().append(
  3393.                             trans.sp.sp.settings.importSettingsDesc + "<br><br>"
  3394.                                 + "<textarea id='sanguSettingsTextArea' style='width: 100%; height: 500px'>"
  3395.                                 + textAreaValue
  3396.                                 + "</textarea>");
  3397.                     } else {
  3398.                         if (textAreaValue.length > 0) {
  3399.                             settingsContainer.val(textAreaValue);
  3400.                         }
  3401.                     }
  3402.                 }
  3403.            
  3404.                 $("#sanguExport").click(function() {
  3405.                     importExportHandler("Export", "Import", JSON.stringify(user_data, null, 4));
  3406.                     $("#sanguSettingsTextArea").select();
  3407.                     return false;
  3408.                 });
  3409.            
  3410.                 $("#sanguImport").click(function() {
  3411.                     var importSanguSettings = $("#importSanguSettings");
  3412.                     importExportHandler("Import", "Export", "");
  3413.            
  3414.                     if (importSanguSettings.length === 0) {
  3415.                         $("#sanguSettingsTextArea")
  3416.                             .after("<br><input type='button' id='importSanguSettings' value='" + trans.sp.sp.settings.importSettings + "'>");
  3417.            
  3418.                         $("#importSanguSettings").click(function() {
  3419.                             var overwriteCurrentSettings = true,
  3420.                                 newUserData;
  3421.            
  3422.                             try {
  3423.                                 newUserData = JSON.parse($("#sanguSettingsTextArea").val());
  3424.            
  3425.                                 if (typeof newUserData.proStyle === 'undefined') {
  3426.                                     overwriteCurrentSettings = confirm(trans.sp.sp.settings.importError + trans.sp.sp.settings.importErrorContinueAnyway);
  3427.                                 }
  3428.            
  3429.                                 if (overwriteCurrentSettings) {
  3430.                                     user_data = $.extend({}, user_data, newUserData);
  3431.                                     pers.set("sangusettings", JSON.stringify(user_data));
  3432.                                     alert(trans.sp.sp.settings.importSettingsSuccess);
  3433.                                 }
  3434.            
  3435.                             } catch (ex) {
  3436.                                 alert(trans.sp.sp.settings.importError + "\n" + ex.message);
  3437.                             }
  3438.                         });
  3439.                     }
  3440.                     $("#sanguSettingsTextArea").focus();
  3441.            
  3442.                     return false;
  3443.                 });
  3444.             })();
  3445.  
  3446.                 switch (current_page.mode) {
  3447.                     case "vacation":
  3448.                 var maxSitDays = server_settings.maxSitDays;
  3449.                 var daysTable = $("table.vis:eq(1)", content_value);
  3450.                 var days = $("td:last", daysTable).text();
  3451.                 days = maxSitDays - parseInt(days.substr(0, days.indexOf(" ")), 10);
  3452.                 if (days > 0) {
  3453.                     var tillTime = new Date();
  3454.                     tillTime.setDate(tillTime.getDate() + days);
  3455.                     daysTable.append("<tr><td>" + trans.sp.rest.sittingAttackTill + "</td><td>" + (tillTime.getDate() + "." + pad(tillTime.getMonth() + 1, 2) + "." + tillTime.getFullYear()) + "</td></tr>");
  3456.                 } else {
  3457.                     daysTable.find("td:last").css("background-color", user_data.colors.error);
  3458.                 }
  3459.                         break;
  3460.  
  3461.                     case "quickbar_edit":
  3462.                 if (user_data.scriptbar.editBoxCols != null && user_data.scriptbar.editBoxCols != false) {
  3463.                     // TODO: textareaIfy: This might be useful on other places aswell. Move to func/UI?
  3464.                     function textareaIfy(element) {
  3465.                         var textarea =
  3466.                             $('<textarea>')
  3467.                                 .attr('cols', Math.round(user_data.scriptbar.editBoxCols / 9))
  3468.                                 .attr('rows', user_data.scriptbar.editBoxRows)
  3469.                                 .val($(element).val());
  3470.                
  3471.                         textarea.change(function () {
  3472.                             element.val($(this).val());
  3473.                         });
  3474.                
  3475.                         element.before(textarea);
  3476.                         $(element).hide();
  3477.                     }
  3478.                
  3479.                     var url = $("form :input[type='text']").css("width", user_data.scriptbar.editBoxCols).last();
  3480.                     textareaIfy(url);
  3481.                 }
  3482.                         break;
  3483.                 }
  3484.  
  3485.                 // SANGU SETTING EDITOR
  3486.                 if (location.href.indexOf('mode=sangu') > -1) {
  3487.                
  3488.                
  3489.                 // settings.editor: : delimited: "array:string" or "required:boolean" etc
  3490.                
  3491.                 function createArraySettingType(inputHandler, index, editor) {
  3492.                     var propertyValueType = editor.split("|")[0];
  3493.                
  3494.                     function deleter() {
  3495.                         inputHandler.getValue().splice(index, 1);
  3496.                     }
  3497.                
  3498.                     switch (propertyValueType) {
  3499.                         case "number":
  3500.                             return new FormInputHandler(inputHandler.formConfig, {
  3501.                                 getter: function() {
  3502.                                     return inputHandler.getValue()[index];
  3503.                                 },
  3504.                                 setter: function(value) {
  3505.                                     value = parseInt(value, 10);
  3506.                                     if (!isNaN(value)) {
  3507.                                         inputHandler.getValue()[index] = value;
  3508.                                     } else {
  3509.                                         inputHandler.getValue()[index] = 0;
  3510.                                     }
  3511.                                 },
  3512.                                 deleter: deleter,
  3513.                                 editor: editor
  3514.                             });
  3515.                
  3516.                         case "text":
  3517.                         case "color":
  3518.                             return new FormInputHandler(inputHandler.formConfig, {
  3519.                                 getter: function() {
  3520.                                     return inputHandler.getValue()[index];
  3521.                                 },
  3522.                                 setter: function(value) {
  3523.                                     inputHandler.getValue()[index] = value;
  3524.                                 },
  3525.                                 deleter: deleter,
  3526.                                 editor: editor
  3527.                             });
  3528.                
  3529.                         case "float":
  3530.                             return new FormInputHandler(inputHandler.formConfig, {
  3531.                                 getter: function() {
  3532.                                     return inputHandler.getValue()[index];
  3533.                                 },
  3534.                                 setter: function(value) {
  3535.                                     value = parseFloat(value.replace(",", "."));
  3536.                                     if (!isNaN(value)) {
  3537.                                         inputHandler.getValue()[index] = value;
  3538.                                     } else {
  3539.                                         inputHandler.getValue()[index] = 0;
  3540.                                     }
  3541.                                 },
  3542.                                 deleter: deleter,
  3543.                                 editor: editor
  3544.                             });
  3545.                     }
  3546.                
  3547.                     alert(propertyValueType + " not supported");
  3548.                 }
  3549.                
  3550.                 /**
  3551.                  * Factory for creating different representations of a property
  3552.                  * @type {string}
  3553.                  */
  3554.                 function createSettingType(inputHandler, editors, editorIndex, arrayOptions) {
  3555.                     var value = inputHandler.getValue(),
  3556.                         decorators,
  3557.                         inputType,
  3558.                         propName;
  3559.                
  3560.                     //array|addNew:number|delete|step=1000
  3561.                     //color=color+number=stack
  3562.                
  3563.                     //q("with -> " + editors + ". isArray: " + isArrayType);
  3564.                     //q(arrayOptions);
  3565.                
  3566.                     if (!arrayOptions.isArrayType) {
  3567.                         decorators = editors.split("|");
  3568.                         inputType = decorators[0];
  3569.                         if (inputType.indexOf("=") > -1) {
  3570.                             propName = inputType.split("=")[1];
  3571.                             inputType = inputType.split("=")[0];
  3572.                         }
  3573.                
  3574.                         //q("isArray: " + propName + "=> " + inputType);
  3575.                         //q(decorators);
  3576.                
  3577.                         switch (inputType) {
  3578.                             case "bool":
  3579.                                 return {
  3580.                                     build: function(id) {
  3581.                                         //assert(typeof value === 'boolean', (typeof value) + ": not a boolean");
  3582.                                         return "<input type='checkbox' id='"+id+"' "+(value ? " checked" : "")+" />";
  3583.                                     },
  3584.                                     bind: function(id) {
  3585.                                         $("#" + id).click(function() {
  3586.                                             inputHandler.setValue($(this).is(":checked"));
  3587.                                         });
  3588.                                     }
  3589.                                 };
  3590.                             case "unit":
  3591.                                 return {
  3592.                                     build: function(id) {
  3593.                                         var i,
  3594.                                             html = "<select id='"+id+"'>";
  3595.                
  3596.                                         for (i = 0; i < world_data.units.length; i++) {
  3597.                                             html += "<option "+(value == world_data.units[i] ? " selected" : "")+">"+world_data.units[i]+"</option>";
  3598.                                         }
  3599.                                         html += "</select>";
  3600.                
  3601.                                         return html;
  3602.                                     },
  3603.                                     bind: function(id) {
  3604.                                         $("#" + id).click(function() {
  3605.                                             inputHandler.setValue($(this).val());
  3606.                                         });
  3607.                                     }
  3608.                                 };
  3609.                             case "text":
  3610.                             case "color":
  3611.                             case "number":
  3612.                             case "float":
  3613.                                 return (function() {
  3614.                                     var htmlString = "",
  3615.                                         index,
  3616.                                         inputBoxSize = 13,
  3617.                                         extraInputAttributes = "",
  3618.                                         extraHtml = "",
  3619.                                         inputTypeAttribute = inputType === "float" ? "number" : inputType;
  3620.                
  3621.                                     switch (inputType) {
  3622.                                         case "text":
  3623.                                             if (typeof value === 'string') {
  3624.                                                 value = value.toString().replace(/'/g, "&#39;");
  3625.                                             }
  3626.                                             inputBoxSize = 50;
  3627.                                             break;
  3628.                                     }
  3629.                                     if (decorators.length > 1) {
  3630.                                         (function() {
  3631.                                             var keyValuePair;
  3632.                                             for (index = 0; index < decorators.length; index++) {
  3633.                                                 keyValuePair = decorators[index].split("=");
  3634.                                                 switch (keyValuePair[0]) {
  3635.                                                     case "delete":
  3636.                                                         extraHtml += " &nbsp;<a href='#' id='{domId}_delete'><img src='graphic/delete.png' title='"+trans.sp.sp.settings.deleteTooltip+"' /></a>";
  3637.                                                         break;
  3638.                                                     case "step":
  3639.                                                         assert(inputType === "float" || inputType === "number", "step only works with numeric inputs");
  3640.                                                         assert(keyValuePair.length === 2, "expected input: step=value");
  3641.                                                         extraInputAttributes += " step='"+keyValuePair[1]+"'";
  3642.                                                         break;
  3643.                                                     case "width":
  3644.                                                         inputBoxSize = keyValuePair[1];
  3645.                                                         break;
  3646.                                                 }
  3647.                                             }
  3648.                                         })();
  3649.                                     }
  3650.                
  3651.                                     htmlString +=
  3652.                                         "<input type='" + inputTypeAttribute + "' id='{domId}' size='" + inputBoxSize +"' "
  3653.                                             + (typeof value !== 'undefined' ? " value='"+value+"'" : "")
  3654.                                             + extraInputAttributes +" />"
  3655.                                             + extraHtml;
  3656.                
  3657.                                     return {
  3658.                                         build: function(id) {
  3659.                                             return htmlString.replace(/\{domId\}/g, id);
  3660.                                         },
  3661.                                         bind: function(id) {
  3662.                                             $("#" + id).change(function() {
  3663.                                                 var newValue = $("#" + id).val();
  3664.                                                 inputHandler.setValue(newValue, editorIndex);
  3665.                                                 return false;
  3666.                                             });
  3667.                
  3668.                                             $("#" + id + "_delete").click(function() {
  3669.                                                 inputHandler.deleteValue();
  3670.                                                 location.reload(false);
  3671.                                             });
  3672.                                         }
  3673.                                     };
  3674.                                 })();
  3675.                         }
  3676.                
  3677.                     } else {
  3678.                         return (function() {
  3679.                             var typesArray = [],
  3680.                                 arrayIndex,
  3681.                                 canAddNew = false,
  3682.                                 inlineDiv = false;
  3683.                
  3684.                             decorators = arrayOptions.decorators;
  3685.                
  3686.                             //q(editors);
  3687.                             //q(decorators);
  3688.                             //q("----");
  3689.                
  3690.                             if (decorators.length > 0) {
  3691.                                 for (arrayIndex = 0; arrayIndex < decorators.length; arrayIndex++) {
  3692.                                     switch (decorators[arrayIndex]) {
  3693.                                         case "addNew":
  3694.                                             canAddNew = true;
  3695.                                             break;
  3696.                                         case "inline":
  3697.                                             inlineDiv = true;
  3698.                                             break;
  3699.                                     }
  3700.                                 }
  3701.                             }
  3702.                
  3703.                             for (arrayIndex = 0; arrayIndex < value.length; arrayIndex++) {
  3704.                                 (function() {
  3705.                                     var fixedArrayIndex = arrayIndex;
  3706.                                     //q("createArraySettingType for" + fixedArrayIndex + "=" +editors);
  3707.                                     typesArray.push(createArraySettingType(inputHandler, fixedArrayIndex, editors));
  3708.                                 })();
  3709.                             }
  3710.                
  3711.                             return {
  3712.                                 build: function(id) {
  3713.                                     var htmlString = "",
  3714.                                         arrayIndex,
  3715.                                         domId;
  3716.                
  3717.                                     assert(typeof value === 'object', (typeof value) +  ": not an object (array)");
  3718.                
  3719.                                     for (arrayIndex = 0; arrayIndex < typesArray.length; arrayIndex++) {
  3720.                                         domId = id + "_" + arrayIndex;
  3721.                
  3722.                                         htmlString += "<div" + (inlineDiv ? " style='display: inline'> &nbsp;" : ">");
  3723.                                         htmlString += typesArray[arrayIndex].build(domId);
  3724.                                         htmlString += "</div>";
  3725.                                     }
  3726.                
  3727.                                     if (canAddNew) {
  3728.                                         htmlString += "<a href='#' id='"+id+"_new'>" + trans.sp.sp.settings.addRecord + "</a>";
  3729.                                     }
  3730.                
  3731.                                     return htmlString;
  3732.                                 },
  3733.                                 bind: function(id) {
  3734.                                     var domId,
  3735.                                         arrayIndex;
  3736.                
  3737.                                     if (canAddNew) {
  3738.                                         $("#" + id + "_new").click(function() {
  3739.                                             var domId = id + "_" + typesArray.length,
  3740.                                                 newType;
  3741.                
  3742.                                             newType = createArraySettingType(inputHandler, typesArray.length, editors);
  3743.                                             $(this).before("<div>" + newType.build(domId) + "</div>");
  3744.                                             newType.bind(domId);
  3745.                                             typesArray.push(newType);
  3746.                
  3747.                                             $("#"+domId).focus();
  3748.                                             return false;
  3749.                                         });
  3750.                                     }
  3751.                
  3752.                                     if (typesArray.length === 0 && canAddNew) {
  3753.                                         $("#" + id + "_new").click();
  3754.                                     } else {
  3755.                                         for (arrayIndex = 0; arrayIndex < typesArray.length; arrayIndex++) {
  3756.                                             domId = id + "_" + arrayIndex;
  3757.                                             typesArray[arrayIndex].bind(domId);
  3758.                                         }
  3759.                                     }
  3760.                                 }
  3761.                             };
  3762.                         })();
  3763.                     }
  3764.                
  3765.                     assert(false, editors + " is not a valid editor");
  3766.                     return;
  3767.                 }
  3768.                
  3769.                 /**
  3770.                  *  @constructor
  3771.                  */
  3772.                 function FormInputHandler(propertyFormConfig, propSettings, editorIndex) {
  3773.                     var arraySplit = propSettings.editor.split(":"),
  3774.                         editors,
  3775.                         arrayOptions = {isArrayType: arraySplit[0].indexOf('array') === 0},
  3776.                         strategy;
  3777.                
  3778.                     assert(typeof propSettings.getter === 'function', 'getter should be a function');
  3779.                     assert(typeof propSettings.setter === 'function', 'setter should be a function');
  3780.                
  3781.                     if (arrayOptions.isArrayType) {
  3782.                         editors = arraySplit[1].split("+");
  3783.                     } else {
  3784.                         editors = arraySplit[0].split("+");
  3785.                     }
  3786.                
  3787.                     //q(editors);
  3788.                
  3789.                     this.formConfig = propertyFormConfig;
  3790.                     if (typeof propertyFormConfig.save === 'undefined') {
  3791.                         this.setValue = propSettings.setter;
  3792.                         this.deleteValue = propSettings.deleter;
  3793.                     } else {
  3794.                         this.setValue = function(value, editorIndex) {
  3795.                             propSettings.setter(value, editorIndex);
  3796.                             propertyFormConfig.save();
  3797.                         };
  3798.                
  3799.                         this.deleteValue = function() {
  3800.                             propSettings.deleter();
  3801.                             propertyFormConfig.save();
  3802.                         };
  3803.                     }
  3804.                
  3805.                     this.getValue = propSettings.getter;
  3806.                     if (true) {
  3807.                         //q("createSettingType for " + editors[0] + " index " + editorIndex);
  3808.                             if (arrayOptions.isArrayType) {
  3809.                                 arrayOptions.decorators = arraySplit[0].split("|");
  3810.                                 arrayOptions.decorators.splice(0, 1);
  3811.                             }
  3812.                
  3813.                             strategy = createSettingType(this, editors[0], editorIndex, arrayOptions);
  3814.                             this.build = strategy.build;
  3815.                             this.bind = strategy.bind;
  3816.                     } else {
  3817.                         (function(inputHandler) {
  3818.                             var handlers = [],
  3819.                                 index;
  3820.                
  3821.                             for (index = 0; index < editors.length; index++) {
  3822.                                 handlers.push(new FormInputHandler(propertyFormConfig, propSettings, index));
  3823.                             }
  3824.                
  3825.                             inputHandler.build = function(id) {
  3826.                                 var i, htmlString = "";
  3827.                                 for (i = 0; i < handlers.length; i++) {
  3828.                                     htmlString += handlers[i].build(id + '_col' + i);
  3829.                                     htmlString += " &nbsp;";
  3830.                                 }
  3831.                                 return htmlString;
  3832.                             };
  3833.                
  3834.                             inputHandler.bind = function(id) {
  3835.                                 var i;
  3836.                                 for (i = 0; i < handlers.length; i++) {
  3837.                                     handlers[i].bind(id + '_col' + i);
  3838.                                 }
  3839.                             };
  3840.                         })(this);
  3841.                     }
  3842.                 }
  3843.                
  3844.                 function buildConfigForm(contentPage, propertyFormConfig) {
  3845.                     var config = propertyFormConfig.properties,
  3846.                         prop,
  3847.                         properties = [],
  3848.                         propIndex,
  3849.                         form = "",
  3850.                         formRow,
  3851.                         container;
  3852.                
  3853.                     // show only relevant properties
  3854.                     // has side-effects
  3855.                     for (prop in config) {
  3856.                         if (config.hasOwnProperty(prop)) {
  3857.                             if (typeof config[prop].show === "undefined" || config[prop].show) {
  3858.                                 config[prop].ownName = prop;
  3859.                                 if (typeof config[prop].type === 'undefined') {
  3860.                                     // this is a variable <-> input form mapping
  3861.                                     config[prop].type = "propertyEditor";
  3862.                                     config[prop].propUI = new FormInputHandler(propertyFormConfig, config[prop].propUI);
  3863.                
  3864.                                 } else {
  3865.                                     // these are other visual indications
  3866.                                     // ui is handled with if / else below
  3867.                                 }
  3868.                
  3869.                                 properties.push(config[prop]);
  3870.                             }
  3871.                         }
  3872.                     }
  3873.                
  3874.                     // build form
  3875.                     form = "<table class='vis' width='100%'>";
  3876.                     form += "<tr><th colspan='2'>" + propertyFormConfig.title + "</th>";
  3877.                     form += "</table>";
  3878.                     form = $(form);
  3879.                
  3880.                     container = $("<div class='propertyEditFormContainer' id='"+propertyFormConfig.id+"' style='display: none' />");
  3881.                     container.append(form).append("<br>");
  3882.                
  3883.                     contentPage.append(container);
  3884.                
  3885.                     for (propIndex = 0; propIndex < properties.length; propIndex++) {
  3886.                         var propUI = properties[propIndex];
  3887.                         if (propUI.type === 'propertyEditor') {
  3888.                             formRow = "<tr>";
  3889.                
  3890.                             // label
  3891.                             formRow += "<td width='25%'>";
  3892.                             if (typeof propUI.tooltip !== "undefined") {
  3893.                                 formRow += "<img src='graphic/questionmark.png' title='"+propUI.tooltip+"' /> &nbsp; ";
  3894.                             }
  3895.                             formRow += propUI.label;
  3896.                             formRow += "</td>";
  3897.                
  3898.                             //editor
  3899.                             if(propUI.label == sangu_trans.mainTagger.otherButtons.hitKey) {
  3900.                         formRow += "<td width='75%'>ctrl + ";
  3901.                         } else {
  3902.                         formRow += "<td width='75%'>";
  3903.                         }
  3904.                             formRow += propUI.propUI.build(propertyFormConfig.id+"_"+propUI.ownName);
  3905.                             formRow += "</td>";
  3906.                
  3907.                             formRow += "</tr>";
  3908.                
  3909.                             form.append(formRow);
  3910.                
  3911.                             // bind the form to the js variable
  3912.                             if (typeof propUI.propUI.bind === 'function') {
  3913.                                 propUI.propUI.bind(propertyFormConfig.id+"_"+propUI.ownName);
  3914.                             }
  3915.                         } else {
  3916.                             switch (propUI.type) {
  3917.                                 case "subtitle":
  3918.                                     form.append("<tr class='row_b'><td colspan='2'><b>"+propUI.label+"</b></td></tr>");
  3919.                                     break;
  3920.                             }
  3921.                         }
  3922.                     }
  3923.                 }
  3924.                 /**
  3925.                  * Contains all translations for the property setting editors
  3926.                  */
  3927.                 var sangu_trans = (function() {
  3928.                     var sangu_trans = {};
  3929.                     sangu_trans.nl = {
  3930.                         main: {
  3931.                             title: "Hoofdgebouw",
  3932.                             villageNames: "Standaard dorpsnamen:",
  3933.                             villageNamesTooltip: "Voeg je veelgebruikte dorpsnamen toe om een dorp met 1 klik te hernoemen.",
  3934.                             villageNameClick: "Autoclick?",
  3935.                             villageNameClickTooltip: "Schakel deze feature uit indien je bijvoorbeeld nog een nummer aan een standaard dorpsnaam wil toevoegen.",
  3936.                             ajaxLoyalty: "Toestemming tonen?"
  3937.                         },
  3938.                         global: {
  3939.                             title: "Op alle pagina's",
  3940.                             tw_version: "Compatibiliteit met TW versie {version} afdwingen",
  3941.                             tw_versionTooltip: "Dit gaat enkel de grijze Sangu bol niet meer tonen. Bugs door de TW update gaan niet op een magische manier opgelost zijn!",
  3942.                             resources: {
  3943.                                 title: "Met kleuren aanduiden hoe vol de opslagplaats is",
  3944.                                 activate: trans.sp.sp.settings.activate,
  3945.                                 blinkWhenStorageFull: "Grondstoffen knipperen wanneer de opslagplaats vol is",
  3946.                                 colors: "Kleurenschakering"
  3947.                             },
  3948.                             incomingsTitle: "Binnenkomende aanvallen/ondersteuning links",
  3949.                             incomingsEditLinks: "Linkdoelwit wijzigen",
  3950.                             incomingsEditLinksTooltip: "De binnenkomende aanval/ondersteuning link wijzigen zodat de eerste 1000 bevelen getoond worden.",
  3951.                             incomingsTrack: "Tijdchecker activeren",
  3952.                             incomingsIndicator: "Tijdchecker tekst",
  3953.                             incomingsIndicatorTooltip: "Gebruik {current} voor het huidig aantal binnenkomende aanvallen. "
  3954.                                 + "{difference} voor het aantal nieuwe aanvallen. "
  3955.                                 + "{saved} voor het laatst opgeslaan aantal aanvallen.",
  3956.                             incomingsIndicatorTooltip2: "Tijdchecker tooltip",
  3957.                             incomingsLastTimeCheckWarning: "Tijdchecker tooltip",
  3958.                             incomingsLastTimeCheckWarningTooltip: "Gebruik {elapsed} voor de verstreken tijd. Gebruik {time} voor de laatste tijdcheck.",
  3959.                             otherSettingsTitle: "Overige configuratie",
  3960.                             visualizeFriends: "Aangeven welke vrienden momenteel online zijn",
  3961.                             duplicateLogoffLink: "Voeg links onderaan een extra 'Afmelden' link toe.",
  3962.                             colorsTitle: "Sangu achtergrondkleuren",
  3963.                             colorsError: "Waarschuwingen",
  3964.                             colorsNeutral: "Neutrale indicaties",
  3965.                             colorsGood: "Positieve indicaties",
  3966.                             colorsSpecial: "Speciale indicaties",
  3967.                             jumperTitle: "Kaartspringer",
  3968.                             jumperAutoOpen: "Het inputveld om coördinaten in te geven direct openen"
  3969.                         },
  3970.                         incoming: {
  3971.                             title: "De standaard TW binnenkomende aanvallen tagger",
  3972.                             autoOpenTagger: "De tagger direct openen indien het bevel nog niet hernoemd is",
  3973.                             forceOpenTagger: "De tagger altijd openen",
  3974.                             renameInputTexbox: "De standaard hernoemde bevelnaam",
  3975.                             renameInputTexboxTooltip: "Legende: {unit}: korte eenheidnaam; {xy} coördinaten; {player}; {village}; {c} Continent; {fields} Afstand in velden; {night} Indicatie wanneer in nachtbonus. Maak leeg om te deactiveren.",
  3976.                             invertSort: "De snelheid mogelijkheden sorteren",
  3977.                             invertSortTooltip: "Vink dit aan om de traagste eenheid bovenaan te plaatsen"
  3978.                         },
  3979.                         overviews: {
  3980.                             addFancyImagesToOverviewLinks: "Icons toevoegen aan de overzichtslinks",
  3981.                             command: {
  3982.                                 title: "Overzichtsscherm: Troepenoverzicht",
  3983.                                 titleOwnTroopsPage: "Pagina's 'Eigen' en 'In het dorp'",
  3984.                                 middleMouseClickDeletesRow: "Actie bij <img src='graphic/command/attack.png'>"
  3985.                                     + " aanklikken met de middelste muisknop: Aangevinkt=rij verwijderen. Uitgevinkt: rode rand rond de \"Opdracht\" cell.",
  3986.                                 middleMouseClickDeletesRowTooltip: "Dit werkt enkel in Opera. Vink dit NIET AAN in Firefox of Chrome!! (De rode rand werkt ook niet in Firefox)",
  3987.                                 titleDefensePage: "Pagina's 'Verdediging' en 'Ondersteuning'",
  3988.                                 changeTroopsOverviewLink: "Link wijzigen om direct 'Eigen troepen' te openen",
  3989.                                 filterMinPopulation: "De standaard ingevulde waarde om te filteren op populatie",
  3990.                                 filterOnUnitTypeSeperator: "Eigen/In het dorp: Filteren op aantal eenheden",
  3991.                                 filterMinDefaultType: "De eenheid om te filteren die standaard geselecteerd is",
  3992.                                 filterMinDefault: "Het aantal eenheden om te filteren dat standaard ingevuld is",
  3993.                                 filterMinDefaultTooltip: "Dit aantal wordt ingevuld wanneer de pagina opent. Bij het selecteren van een andere eenheid worden de waarden hieronder gebruikt.",
  3994.                                 filterMinOther: "De standaard waarde voor de overige eenheden",
  3995.                                 filterAutoSort: "De dorpenlijst automatisch sorteren na het ingeven van een doeldorp"
  3996.                             },
  3997.                             troopsRestack: {
  3998.                                 title: "Alle pagina's: Stack BBCodes generatie",
  3999.                                 to: "Stack een dorp tot hoeveel populatie",
  4000.                                 requiredDifference: "Vereist verschil in huidige populatie in het dorp en de waarde hierboven",
  4001.                                 fieldsDistanceFilterDefault: "Het standaard ingevuld aantal velden waarop gefilterd wordt",
  4002.                                 filterReverse: "Aangevinkt: Rijen die voldoen aan de zoekopdracht tonen. Anders de rijen verbergen.",
  4003.                                 filterReverseTooltip: "Dit kan op de pagina zelf nog aangepast worden",
  4004.                                 defaultPopulationFilterAmount: "Het standaard ingevuld aantal voor de populatie filter",
  4005.                                 removeRowsWithoutSupport: "Bij het berekenen van de totalen dorpen zonder ondersteuning direct verbergen",
  4006.                                 autohideWithoutSupportAfterFilter: "Automatisch dorpen zonder overige ondersteuning wegfilteren na het toepassen van een filter",
  4007.                                 autohideWithoutSupportAfterFilterTooltip: "Bij de aanvalsfilter worden de dorpen die niet onder aanval zijn sowieso verborgen.",
  4008.                                 calculateDefTotalsAfterFilter: "Automatisch de totale ondersteuning per dorp berekenen na het toepassen van een filter",
  4009.                                 calculateDefTotalsAfterFilterTooltip: "Voor sommige filters moeten de totalen sowieso toch eerst berekend worden."
  4010.                             },
  4011.                             commands: {
  4012.                                 title: "Overzichtsscherm: Bevelen",
  4013.                                 sumRow: "Een scheidingsrij tussen verschillende dorpen toevoegen",
  4014.                                 filterFakeMaxPop: "Een bevel met minder dan deze populatie is een fake aanval (en wordt verborgen)",
  4015.                                 requiredTroopAmount: "Bij de export naar BBCodes worden bevelen met minder dan deze populatie overgeslagen."
  4016.                             },
  4017.                             resources: {
  4018.                                 title: "Overzichtsscherm: Productie",
  4019.                                 requiredResDefault: "Het aantal grondstoffen dat standaard in het invoerveld ingevuld is",
  4020.                                 requiredMerchants: "Het aantal handelaren dat standaard ingevuld is",
  4021.                                 filterMerchants: "Ook filteren op aantal beschikbare handelaren",
  4022.                                 filterRows: "Aanvinken om rijen te verbergen. Wanneer uitgevinkt wordt achtergrondkleur van de grondstoffen gewijzigd",
  4023.                                 bbcodeMinimumDiff: "Het minimum verschil in grondstoffen tussen het aantal in het dorp en het aantal waarop gefilterd wordt voordat het dorp in de BBcode export opgenomen wordt",
  4024.                                 highlightColor: "De achtergrondkleur die gebruikt wordt om de grondstoffen aan te duiden die voldoen aan de filtercriteria"
  4025.                             },
  4026.                             buildings: {
  4027.                                 title: "Overzichtsscherm: Gebouwen",
  4028.                                 minMaxTitle: "Geef de laagst en hoogst aanvaardbare levels voor je gebouwen",
  4029.                                 minLevel: "Min {building}",
  4030.                                 maxLevel: "Max {building}"
  4031.                             },
  4032.                             incomings: {
  4033.                                 title: "Overzichtsscherm: Aankomend",
  4034.                                 attackIdTitle: "Groeperen op aanvalsid",
  4035.                                 minValueTooltip: "De te tonen tekst wanneer het verschil in aanvalsid tussen de vorige binnenkomende aanval meer is dan het aangegeven verschil",
  4036.                                 seperatorTitle: "Bij verschil &lt; {minValue}",
  4037.                                 minValue: "Minimum verschil",
  4038.                                 text: "Tekst",
  4039.                                 attackIdHigherDescription: "Te tonen tekst bij groter verschil in aanvalsid"
  4040.                             }
  4041.                         },
  4042.                         place: {
  4043.                             title: "Plaats: Speciale troepen invul links",
  4044.                             titleCustom: "Plaats: Extra troepen invul links",
  4045.                             linkText: "Link tekst",
  4046.                             link: "Link: {name}",
  4047.                
  4048.                             scoutTitle: "Verkenner links",
  4049.                             scoutVillage: "Een dorp krijgt verkenner links als er meer verkenners zijn dan dit",
  4050.                             scoutPlaceLinks: "Links om zoveel verkenners in te vullen",
  4051.                
  4052.                             fakePlaceLinkTitle: "Fake troepen link",
  4053.                             fakePlaceExcludeTroops: "Type troepen te negeren bij selecteren",
  4054.                             fakePlaceExcludeTroopsTooltip: "Gebruik de namen: spear, sword, axe, archer, sword, spy, light, marcher, heavy, ram, catapult",
  4055.                
  4056.                             noblePlaceLinkTitle: "Edel links",
  4057.                             noblePlaceLinkFirstTitle: "Edel link met het meeste troepen",
  4058.                             noblePlaceLinkFirstNameTooltip: "Er blijven genoeg troepen thuis om voor de resterende edels nog ondersteuning te kunnen sturen. Maak leeg om deze link niet te tonen.",
  4059.                
  4060.                             noblePlaceLinkSupportTitle: "Edel link met minimale ondersteuning",
  4061.                             noblePlaceLinksForceShow: "Ook tonen wanneer er slechts 1 edel in het dorp is",
  4062.                             nobleSupportOffTitle: "Edel ondersteuning voor offensief dorp",
  4063.                             nobleSupportDefTitle: "Edel ondersteuning voor defensief dorp",
  4064.                             nobleSupportUnit: "Eenheid",
  4065.                             nobleSupportAmount: "Aantal eenheden",
  4066.                
  4067.                             noblePlaceLinkDivideTitle: "Edel link met gelijk verdeelde ondersteuning",
  4068.                             noblePlaceLinkDivideAddRam: "Rammen mee selecteren",
  4069.                
  4070.                             customPlaceLinksTitle: "Andere links",
  4071.                             customPlaceOneTimeTooltip: "Vul een getal in om zoveel te sturen. Vul een negatief getal om zoveel te laten staan.",
  4072.                             customPlaceSendAlong: "Meesturen tot",
  4073.                             customPlaceSendAlongTooltip: "Indien er na selectie van bovenstaande troepen minder dan zoveel troepen in het dorp zou overblijven, selecteer dan alle troepen"
  4074.                         },
  4075.                         profile: {
  4076.                             title: "Verfraaien van het profiel van spelers en stammen",
  4077.                             moveClaim: "Verplaats dorpsclaim zodat alle andere links op dezelfde plaats blijven staan",
  4078.                             mapLink: {
  4079.                                 title: "Kaarteigenschappen van link naar TWMaps kaart generator",
  4080.                                 fill: "Achtergrondkleur",
  4081.                                 zoom: 'Inzoom niveau',
  4082.                                 grid: 'Continentlijnen',
  4083.                                 gridContinentNumbers: "Continent nummers",
  4084.                                 playerColor: 'Spelerskleur',
  4085.                                 tribeColor: 'Zijn stam kleur',
  4086.                                 centreX: "Centreren op X coördinaat",
  4087.                                 centreY: "Centreren op Y coördinaat",
  4088.                                 ownColor: 'Eigen kleur',
  4089.                                 markedOnly: "Alleen gemarkeerde",
  4090.                                 yourTribeColor: "Eigen stam kleur",
  4091.                                 bigMarkers: "Grotere aanduidingen"
  4092.                             },
  4093.                             popup: {
  4094.                                 title: "Overnames popup",
  4095.                                 width: "Breedte van de popup",
  4096.                                 height: "Hoogte van de popup",
  4097.                                 left: "Horizontale positie",
  4098.                                 top: "Verticale positie"
  4099.                             }
  4100.                         },
  4101.                         mainTagger: {
  4102.                             title: "Tagger op dorpsoverzicht",
  4103.                             autoOpen: "De tagger automatisch openen bij binnenkomende aanvallen",
  4104.                             inputBoxWidth: "De breedte van de bevel hernoemings inputvelden",
  4105.                             defaultDescription: "De naam die standaard in het hernoemings inputveld geplaatst wordt",
  4106.                             defaultDescriptionTooltip: "Gebruik {xy} voor de coordinaten van het herkomst dorp",
  4107.                             autoOpenCommands: "De bevel hernoemings inputvelden direct tonen",
  4108.                             minutesDisplayDodgeTimeOnMap: "Aantal minuten dat de laatste dodgetijd op de kaart getoond wordt",
  4109.                             minutesDisplayDodgeTimeOnMapTooltip: "De laatste dodgetijd is de tijd van het laatste bevel, aangeduidt met gewijzigde achtergrondkleur na het herbenoemen van binnenkomende aanvallen.",
  4110.                             minutesWithoutAttacksDottedLine: "Elke zoveel minuten zonder een tussenliggende binnenkomende aanval aanduiden met een stippelijn (180 = 3 uur)",
  4111.                             colorSupport: "Binnenkomende ondersteuning een andere achtergrondkleur geven",
  4112.                             keepReservedWords: "Tekst die vervangen wordt door een eenheid icon behouden bij hernoemen binnenkomende aanval",
  4113.                             keepReservedWordsTooltip: "Bijvoorbeeld \"Verk.\" wordt vervangen door een verkenners icon",
  4114.                             otherButtons: {
  4115.                                 title: "Andere hernoemings knoppen",
  4116.                                 renameTo: "Hernoemen naar",
  4117.                                 button: "Tekst knop",
  4118.                                 hitKey: "Sneltoets"
  4119.                             }
  4120.                         },
  4121.                         confirm: {
  4122.                             title: "Bevel comfirmatie pagina",
  4123.                             addExtraOkButton: "Links bovenaan de pagina een extra OK knop toevoegen",
  4124.                             replaceNightBonus: "Nachtbonus melding verwerken in de pagina titel",
  4125.                             replaceTribeClaim: "Dorpsclaim melding verwerken in de pagina titel",
  4126.                             addCatapultImages: "Gebouws iconen tonen om snel het katapult doelwit te wijzigen"
  4127.                         },
  4128.                         villageInfo: {
  4129.                             title: "Extra links naar het troepenoverzicht",
  4130.                             title2: "Twee extra links naar het troepenoverzicht op elke dorpsinformatie pagina toevoegen",
  4131.                             icon: "Kies een icoon",
  4132.                             off_title: "Extra link voor aanvallen",
  4133.                             def_title: "Extra link voor verdedigen",
  4134.                             linkName: "De link die toegevoegd wordt",
  4135.                             group: "Binnen welk groep id openen (kies 0 voor alle groepen)",
  4136.                             groupTitle: "Zodra je meer dan 1000 dorpen bezit worden enkel die eerste 1000 getoond & gefilterd. Door een groep id in te geven (vb je groep \"aanvalsdorpen\" of \"verdedigingsdorpen\") wordt de troepenlijst binnen deze groep geopend.",
  4137.                             activateFilter: "Filter activeren",
  4138.                             filter: {
  4139.                                 title: "Direct filteren",
  4140.                                 unit: "Eenheid",
  4141.                                 amount: "Minimale hoeveelheid"
  4142.                             },
  4143.                             sort: "Dorpenlijst direct sorteren",
  4144.                             changeSpeed: "Traagste eenheid snelheid wijzigen"
  4145.                         },
  4146.                         other: {
  4147.                             title: "Overige configuratie",
  4148.                             proStyle: "Pro Style?",
  4149.                             proStyleTooltip: "Met deze setting worden een heleboel kleinere features aan of uitgeschakeld",
  4150.                             timeDisplayTitle: "Hoe looptijden weergeven",
  4151.                             displayDays: "Dagen tonen wanneer de troepen langer dan 24 uur lopen?",
  4152.                             displayDaysTooltip: "Voorbeeld: toon \"1.18:01:36\" wanneer aangevinkt, zoniet wordt die looptijd als \"42:01:36\" weergegeven",
  4153.                             walkingTimeDisplay: "Te tonen tekst",
  4154.                             walkingTimeDisplayTooltip: "Gebruik {duration} voor het aantal uren en {arrival} voor de aankomstdatum",
  4155.                             calculateSnob: "Berekenen hoeveel edels direct kunnen geproduceerd worden",
  4156.                             showPlayerProfileOnVillage: "Het uitgebreide spelersprofiel tonen op een dorpsinformatie pagina",
  4157.                             farmLimitTitle: "Dorpstacks achtergrondkleuren",
  4158.                             farmLimitStackColors: "Kleurenschakering",
  4159.                             farmLimitAcceptableOverstack: "Acceptabele overstack voor elke kleurschakering",
  4160.                             farmLimitAcceptableOverstackTooltip: "Boerderijlimiet: {farmlimit}",
  4161.                             farmLimitUnlimitedStack: "Aantal populatie voor elke kleurschakering",
  4162.                             ajaxSeperateSupport: "Dorpsoverzicht: Visualiseer het verschil tussen eigen en ondersteunende troepen in het dorpsoverzicht",
  4163.                             canHideDiv: "Dorpsoverzicht: Een extra X icon toevoegen om een info kader volledig te verwijderen",
  4164.                             commandRenamer: "Bevelen automatisch hernoemen",
  4165.                             commandRenamerActive: "De verzonden troepen in de bevelnaam weergeven",
  4166.                             commandRenamerAddHaul: "De buit aan de bevelnaam toevoegen"
  4167.                         }
  4168.                     };
  4169.                
  4170.                     //sangu_trans.de = {};
  4171.                
  4172.                     /*sangu_trans.en = {
  4173.                         main: {
  4174.                             title: "Main building",
  4175.                             villageNames: "Village names:",
  4176.                             villageNamesTooltip: "Add village names to the village headquarters to quickly edit the village name to a preset name.",
  4177.                             villageNameClick: "Autoclick?",
  4178.                             villageNameClickTooltip: "true: one of the previous button clicked automatically changes the village name. false: only fills in the name in the textbox but does not click the button",
  4179.                             ajaxLoyalty: "Show loyalty?",
  4180.                             ajaxLoyaltyTooltip: "Get the loyalty at the building construction/destruction page"
  4181.                         }
  4182.                     };*/
  4183.                
  4184.                     return sangu_trans[game_data.market];
  4185.                 }());
  4186.                 /**
  4187.                  * Configuration array containing the metadata for generating the different
  4188.                  * property editor UIs
  4189.                  */
  4190.                 var user_data_configs = (function() {
  4191.                         /**
  4192.                          * Debug bool
  4193.                          */
  4194.                     var showConfigs = true,
  4195.                         user_data_configs = [],
  4196.                         sangu_saver = function() {
  4197.                             pers.set('sangusettings', JSON.stringify(user_data));
  4198.                             trackEvent("ScriptUsage", "SettingEdit", "1");
  4199.                         };
  4200.                
  4201.                     if (showConfigs) {
  4202.                         user_data_configs.push({
  4203.                             id: "global",
  4204.                             title: sangu_trans.global.title,
  4205.                             save: sangu_saver,
  4206.                             properties: {
  4207.                                 twVersion: {
  4208.                                     label: sangu_trans.global.tw_version.replace("{version}", game_data.majorVersion),
  4209.                                     tooltip: sangu_trans.global.tw_versionTooltip,
  4210.                                     propUI: {
  4211.                                         getter: function() { return pers.get("forceCompatibility") !== '' && pers.get("forceCompatibility") === 'true'; },
  4212.                                         setter: function(value) { pers.set("forceCompatibility", value); },
  4213.                                         editor: "bool"
  4214.                                     }
  4215.                                 },
  4216.                                 resourcesTitle: {
  4217.                                     type: "subtitle",
  4218.                                     label: sangu_trans.global.resources.title
  4219.                                 },
  4220.                                 resourcesActivate: {
  4221.                                     label: sangu_trans.global.resources.activate,
  4222.                                     propUI: {
  4223.                                         getter: function() { return user_data.global.resources.active; },
  4224.                                         setter: function(value) { user_data.global.resources.active = value; },
  4225.                                         editor: "bool"
  4226.                                     }
  4227.                                 },
  4228.                                 resourceColors: {
  4229.                                     label: sangu_trans.global.resources.colors,
  4230.                                     propUI: {
  4231.                                         getter: function() { return user_data.global.resources.backgroundColors; },
  4232.                                         setter: function(value) { user_data.global.resources.backgroundColors = value; },
  4233.                                         editor: "array|inline:color"
  4234.                                     }
  4235.                                 },
  4236.                                 resourcesBlinkWhenFull: {
  4237.                                     label: sangu_trans.global.resources.blinkWhenStorageFull,
  4238.                                     propUI: {
  4239.                                         getter: function() { return user_data.global.resources.blinkWhenStorageFull; },
  4240.                                         setter: function(value) { user_data.global.resources.blinkWhenStorageFull = value; },
  4241.                                         editor: "bool"
  4242.                                     }
  4243.                                 },
  4244.                                 incomingsTitle: {
  4245.                                     type: "subtitle",
  4246.                                     label: sangu_trans.global.incomingsTitle
  4247.                                 },
  4248.                                 incomingsEditLinks: {
  4249.                                     label: sangu_trans.global.incomingsEditLinks,
  4250.                                     tooltip: sangu_trans.global.incomingsEditLinksTooltip,
  4251.                                     propUI: {
  4252.                                         getter: function() { return user_data.global.incomings.editLinks; },
  4253.                                         setter: function(value) { user_data.global.incomings.editLinks = value; },
  4254.                                         editor: "bool"
  4255.                                     }
  4256.                                 },
  4257.                                 incomingsTrack: {
  4258.                                     label: sangu_trans.global.incomingsTrack,
  4259.                                     propUI: {
  4260.                                         getter: function() { return user_data.global.incomings.track; },
  4261.                                         setter: function(value) { user_data.global.incomings.track = value; },
  4262.                                         editor: "bool"
  4263.                                     }
  4264.                                 },
  4265.                                 incomingsIndicator: {
  4266.                                     label: sangu_trans.global.incomingsIndicator,
  4267.                                     tooltip: sangu_trans.global.incomingsIndicatorTooltip,
  4268.                                     propUI: {
  4269.                                         getter: function() { return user_data.global.incomings.indicator; },
  4270.                                         setter: function(value) { user_data.global.incomings.indicator = value; },
  4271.                                         editor: "text"
  4272.                                     }
  4273.                                 },
  4274.                                 incomingsIndicatorTooltip2: {
  4275.                                     label: sangu_trans.global.incomingsIndicatorTooltip2,
  4276.                                     propUI: {
  4277.                                         getter: function() { return user_data.global.incomings.indicatorTooltip; },
  4278.                                         setter: function(value) { user_data.global.incomings.indicatorTooltip = value; },
  4279.                                         editor: "text"
  4280.                                     }
  4281.                                 },
  4282.                                 lastTimeCheckWarning: {
  4283.                                     label: sangu_trans.global.incomingsLastTimeCheckWarning,
  4284.                                     tooltip: sangu_trans.global.incomingsLastTimeCheckWarningTooltip,
  4285.                                     propUI: {
  4286.                                         getter: function() { return user_data.global.incomings.lastTimeCheckWarning; },
  4287.                                         setter: function(value) { user_data.global.incomings.lastTimeCheckWarning = value; },
  4288.                                         editor: "text"
  4289.                                     }
  4290.                                 },
  4291.                                 jumperTitle: {
  4292.                                     type: "subtitle",
  4293.                                     label: sangu_trans.global.jumperTitle
  4294.                                 },
  4295.                                 jumperActivate: {
  4296.                                     label: trans.sp.sp.settings.activate,
  4297.                                     propUI: {
  4298.                                         getter: function() { return user_data.jumper.enabled; },
  4299.                                         setter: function(value) { user_data.jumper.enabled = value; },
  4300.                                         editor: "bool"
  4301.                                     }
  4302.                                 },
  4303.                                 jumperAutoOpen: {
  4304.                                     label: sangu_trans.global.jumperAutoOpen,
  4305.                                     propUI: {
  4306.                                         getter: function() { return user_data.jumper.autoShowInputbox; },
  4307.                                         setter: function(value) { user_data.jumper.autoShowInputbox = value; },
  4308.                                         editor: "bool"
  4309.                                     }
  4310.                                 },
  4311.                                 colorsTitle: {
  4312.                                     type: "subtitle",
  4313.                                     label: sangu_trans.global.colorsTitle
  4314.                                 },
  4315.                                 colorsError: {
  4316.                                     label: sangu_trans.global.colorsError,
  4317.                                     propUI: {
  4318.                                         getter: function() { return user_data.colors.error; },
  4319.                                         setter: function(value) { user_data.colors.error = value; },
  4320.                                         editor: "color"
  4321.                                     }
  4322.                                 },
  4323.                                 colorsGood: {
  4324.                                     label: sangu_trans.global.colorsGood,
  4325.                                     propUI: {
  4326.                                         getter: function() { return user_data.colors.good; },
  4327.                                         setter: function(value) { user_data.colors.good = value; },
  4328.                                         editor: "color"
  4329.                                     }
  4330.                                 },
  4331.                                 colorsNeutral: {
  4332.                                     label: sangu_trans.global.colorsNeutral,
  4333.                                     propUI: {
  4334.                                         getter: function() { return user_data.colors.neutral; },
  4335.                                         setter: function(value) { user_data.colors.neutral = value; },
  4336.                                         editor: "color"
  4337.                                     }
  4338.                                 },
  4339.                                 colorsSpecial: {
  4340.                                     label: sangu_trans.global.colorsSpecial,
  4341.                                     propUI: {
  4342.                                         getter: function() { return user_data.colors.special; },
  4343.                                         setter: function(value) { user_data.colors.special = value; },
  4344.                                         editor: "color"
  4345.                                     }
  4346.                                 },
  4347.                                 otherSettingsTitle: {
  4348.                                     type: "subtitle",
  4349.                                     label: sangu_trans.global.otherSettingsTitle
  4350.                                 },
  4351.                                 visualizeFriends: {
  4352.                                     label: sangu_trans.global.visualizeFriends,
  4353.                                     propUI: {
  4354.                                         getter: function() { return user_data.global.visualizeFriends; },
  4355.                                         setter: function(value) { user_data.global.visualizeFriends = value; },
  4356.                                         editor: "bool"
  4357.                                     }
  4358.                                 },
  4359.                                 duplicateLogoffLink: {
  4360.                                     label: sangu_trans.global.duplicateLogoffLink,
  4361.                                     propUI: {
  4362.                                         getter: function() { return user_data.global.duplicateLogoffLink; },
  4363.                                         setter: function(value) { user_data.global.duplicateLogoffLink = value; },
  4364.                                         editor: "bool"
  4365.                                     }
  4366.                                 }
  4367.                             }
  4368.                         });
  4369.                     }
  4370.                
  4371.                     if (showConfigs) {
  4372.                         user_data_configs.push({
  4373.                             id: "main",
  4374.                             title: sangu_trans.main.title,
  4375.                             save: sangu_saver,
  4376.                             properties: {
  4377.                                 villageNames: {
  4378.                                     tooltip: sangu_trans.main.villageNamesTooltip,
  4379.                                     label: sangu_trans.main.villageNames,
  4380.                                     propUI: {
  4381.                                         getter: function() { return user_data.main.villageNames; },
  4382.                                         setter: function(value) { user_data.main.villageNames = value; },
  4383.                                         editor: "array|addNew:text|delete"
  4384.                                     }
  4385.                                 },
  4386.                                 villageNameClick: {
  4387.                                     tooltip: sangu_trans.main.villageNameClickTooltip,
  4388.                                     label: sangu_trans.main.villageNameClick,
  4389.                                     propUI: {
  4390.                                         getter: function() { return user_data.main.villageNameClick; },
  4391.                                         setter: function(value) { user_data.main.villageNameClick = value; },
  4392.                                         editor: "bool"
  4393.                                     }
  4394.                                 },
  4395.                                 ajaxLoyalty: {
  4396.                                     label: sangu_trans.main.ajaxLoyalty,
  4397.                                     show: server_settings.ajaxAllowed,
  4398.                                     propUI: {
  4399.                                         getter: function() { return user_data.main.ajaxLoyalty; },
  4400.                                         setter: function(value) { user_data.main.ajaxLoyalty = value; },
  4401.                                         editor: "bool"
  4402.                                     }
  4403.                                 }
  4404.                             }
  4405.                         });
  4406.                     }
  4407.                
  4408.                     if (showConfigs) {
  4409.                         user_data_configs.push({
  4410.                             id: "incoming",
  4411.                             title: sangu_trans.incoming.title,
  4412.                             save: sangu_saver,
  4413.                             properties: {
  4414.                                 autoOpenTagger: {
  4415.                                     label: sangu_trans.incoming.autoOpenTagger,
  4416.                                     propUI: {
  4417.                                         getter: function() { return user_data.incoming.autoOpenTagger; },
  4418.                                         setter: function(value) { user_data.incoming.autoOpenTagger = value; },
  4419.                                         editor: "bool"
  4420.                                     }
  4421.                                 },
  4422.                                 forceOpenTagger: {
  4423.                                     label: sangu_trans.incoming.forceOpenTagger,
  4424.                                     propUI: {
  4425.                                         getter: function() { return user_data.incoming.forceOpenTagger; },
  4426.                                         setter: function(value) { user_data.incoming.forceOpenTagger = value; },
  4427.                                         editor: "bool"
  4428.                                     }
  4429.                                 },
  4430.                                 renameInputTexbox: {
  4431.                                     label: sangu_trans.incoming.renameInputTexbox,
  4432.                                     tooltip: sangu_trans.incoming.renameInputTexboxTooltip,
  4433.                                     propUI: {
  4434.                                         getter: function() { return user_data.incoming.renameInputTexbox; },
  4435.                                         setter: function(value) { user_data.incoming.renameInputTexbox = value; },
  4436.                                         editor: "text"
  4437.                                     }
  4438.                                 },
  4439.                                 invertSort: {
  4440.                                     label: sangu_trans.incoming.invertSort,
  4441.                                     tooltip: sangu_trans.incoming.invertSortTooltip,
  4442.                                     propUI: {
  4443.                                         getter: function() { return user_data.incoming.invertSort; },
  4444.                                         setter: function(value) { user_data.incoming.invertSort = value; },
  4445.                                         editor: "bool"
  4446.                                     }
  4447.                                 }
  4448.                             }
  4449.                         });
  4450.                     }
  4451.                
  4452.                     if (showConfigs) {
  4453.                         (function() {
  4454.                             var properties = {
  4455.                                 activate: {
  4456.                                     label: sangu_trans.global.resources.activate,
  4457.                                     propUI: {
  4458.                                         getter: function() { return user_data.mainTagger2.active; },
  4459.                                         setter: function(value) { user_data.mainTagger2.active = value; },
  4460.                                         editor: "bool"
  4461.                                     }
  4462.                                 },
  4463.                                 autoOpen: {
  4464.                                     label: sangu_trans.mainTagger.autoOpen,
  4465.                                     propUI: {
  4466.                                         getter: function() { return user_data.mainTagger2.autoOpen; },
  4467.                                         setter: function(value) { user_data.mainTagger2.autoOpen = value; },
  4468.                                         editor: "bool"
  4469.                                     }
  4470.                                 },
  4471.                                 inputBoxWidth: {
  4472.                                     label: sangu_trans.mainTagger.inputBoxWidth,
  4473.                                     propUI: {
  4474.                                         getter: function() { return user_data.mainTagger2.inputBoxWidth; },
  4475.                                         setter: function(value) { user_data.mainTagger2.inputBoxWidth = value; },
  4476.                                         editor: "number|step=5"
  4477.                                     }
  4478.                                 },
  4479.                                 autoOpenCommands: {
  4480.                                     label: sangu_trans.mainTagger.autoOpenCommands,
  4481.                                     propUI: {
  4482.                                         getter: function() { return user_data.mainTagger2.autoOpenCommands; },
  4483.                                         setter: function(value) { user_data.mainTagger2.autoOpenCommands = value; },
  4484.                                         editor: "bool"
  4485.                                     }
  4486.                                 },
  4487.                                 minutesDisplayDodgeTimeOnMap: {
  4488.                                     label: sangu_trans.mainTagger.minutesDisplayDodgeTimeOnMap,
  4489.                                     tooltip: sangu_trans.mainTagger.minutesDisplayDodgeTimeOnMapTooltip,
  4490.                                     propUI: {
  4491.                                         getter: function() { return user_data.mainTagger2.minutesDisplayDodgeTimeOnMap; },
  4492.                                         setter: function(value) { user_data.mainTagger2.minutesDisplayDodgeTimeOnMap = value; },
  4493.                                         editor: "number"
  4494.                                     }
  4495.                                 },
  4496.                                 minutesWithoutAttacksDottedLine: {
  4497.                                     label: sangu_trans.mainTagger.minutesWithoutAttacksDottedLine,
  4498.                                     propUI: {
  4499.                                         getter: function() { return user_data.mainTagger2.minutesWithoutAttacksDottedLine; },
  4500.                                         setter: function(value) { user_data.mainTagger2.minutesWithoutAttacksDottedLine = value; },
  4501.                                         editor: "number|step=60"
  4502.                                     }
  4503.                                 },
  4504.                                 colorSupport: {
  4505.                                     label: sangu_trans.mainTagger.colorSupport,
  4506.                                     propUI: {
  4507.                                         getter: function() { return user_data.mainTagger2.colorSupport; },
  4508.                                         setter: function(value) { user_data.mainTagger2.colorSupport = value; },
  4509.                                         editor: "color"
  4510.                                     }
  4511.                                 },
  4512.                                 defaultDescription: {
  4513.                                     label: sangu_trans.mainTagger.defaultDescription,
  4514.                                     tooltip: sangu_trans.mainTagger.defaultDescriptionTooltip,
  4515.                                     propUI: {
  4516.                                         getter: function() { return user_data.mainTagger2.defaultDescription; },
  4517.                                         setter: function(value) { user_data.mainTagger2.defaultDescription = value; },
  4518.                                         editor: "text"
  4519.                                     }
  4520.                                 },
  4521.                                 keepReservedWords: {
  4522.                                     label: sangu_trans.mainTagger.keepReservedWords,
  4523.                                     tooltip: sangu_trans.mainTagger.keepReservedWordsTooltip,
  4524.                                     propUI: {
  4525.                                         getter: function() { return user_data.mainTagger2.keepReservedWords; },
  4526.                                         setter: function(value) { user_data.mainTagger2.keepReservedWords = value; },
  4527.                                         editor: "bool"
  4528.                                     }
  4529.                                 },
  4530.                                 otherButtonsTitle: {
  4531.                                     type: "subtitle",
  4532.                                     label: sangu_trans.mainTagger.otherButtons.title
  4533.                                 }
  4534.                             };
  4535.                
  4536.                             for (var i = 0; i < user_data.mainTagger2.otherDescs.length; i++) {
  4537.                                 (function() {
  4538.                                     var otherDescription = user_data.mainTagger2.otherDescs[i];
  4539.                
  4540.                                     properties['otherButton'+i] = {
  4541.                                         type: "subtitle",
  4542.                                         label: sangu_trans.mainTagger.otherButtons.title + ": " + otherDescription.name
  4543.                                     }
  4544.                
  4545.                                     properties['otherButtonActive'+i] = {
  4546.                                         label: sangu_trans.global.resources.activate,
  4547.                                         propUI: {
  4548.                                             getter: function() { return otherDescription.active; },
  4549.                                             setter: function(value) { otherDescription.active = value; },
  4550.                                             editor: "bool"
  4551.                                         }
  4552.                                     }
  4553.                
  4554.                                     properties['otherButtonName'+i] = {
  4555.                                         label: sangu_trans.mainTagger.otherButtons.button,
  4556.                                         propUI: {
  4557.                                             getter: function() { return otherDescription.name; },
  4558.                                             setter: function(value) { otherDescription.name = value; },
  4559.                                             editor: "text|width=10"
  4560.                                         }
  4561.                                     }
  4562.                                    
  4563.                                     properties['otherButtonHitKey'+i] = {
  4564.                                         label: sangu_trans.mainTagger.otherButtons.hitKey,
  4565.                                         propUI: {
  4566.                                             getter: function() { return otherDescription.hitKey; },
  4567.                                             setter: function(value) { otherDescription.hitKey = value; },
  4568.                                             editor: "text|width=4"
  4569.                                         }
  4570.                                     }
  4571.                
  4572.                                     properties['otherButtonDesc'+i] = {
  4573.                                         label: sangu_trans.mainTagger.otherButtons.renameTo,
  4574.                                         propUI: {
  4575.                                             getter: function() { return otherDescription.renameTo; },
  4576.                                             setter: function(value) { otherDescription.renameTo = value; },
  4577.                                             editor: "text|width=50"
  4578.                                         }
  4579.                                     }
  4580.                                 }());
  4581.                             }
  4582.                
  4583.                
  4584.                             user_data_configs.push({
  4585.                                 id: "mainTagger",
  4586.                                 title: sangu_trans.mainTagger.title,
  4587.                                 save: sangu_saver,
  4588.                                 properties: properties
  4589.                             });
  4590.                         }());
  4591.                     }
  4592.                
  4593.                     if (showConfigs) {
  4594.                         user_data_configs.push({
  4595.                             id: "confirm",
  4596.                             title: sangu_trans.confirm.title,
  4597.                             save: sangu_saver,
  4598.                             properties: {
  4599.                                 addExtraOkButton: {
  4600.                                     label: sangu_trans.confirm.addExtraOkButton,
  4601.                                     propUI: {
  4602.                                         getter: function() { return user_data.confirm.addExtraOkButton; },
  4603.                                         setter: function(value) { user_data.confirm.addExtraOkButton = value; },
  4604.                                         editor: "bool"
  4605.                                     }
  4606.                                 },
  4607.                                 replaceNightBonus: {
  4608.                                     label: sangu_trans.confirm.replaceNightBonus,
  4609.                                     show: world_config.nightbonus.active,
  4610.                                     propUI: {
  4611.                                         getter: function() { return user_data.confirm.replaceNightBonus; },
  4612.                                         setter: function(value) { user_data.confirm.replaceNightBonus = value; },
  4613.                                         editor: "bool"
  4614.                                     }
  4615.                                 },
  4616.                                 replaceTribeClaim: {
  4617.                                     label: sangu_trans.confirm.replaceTribeClaim,
  4618.                                     propUI: {
  4619.                                         getter: function() { return user_data.confirm.replaceTribeClaim; },
  4620.                                         setter: function(value) { user_data.confirm.replaceTribeClaim = value; },
  4621.                                         editor: "bool"
  4622.                                     }
  4623.                                 },
  4624.                                 addCatapultImages: {
  4625.                                     label: sangu_trans.confirm.addCatapultImages,
  4626.                                     propUI: {
  4627.                                         getter: function() { return user_data.confirm.addCatapultImages; },
  4628.                                         setter: function(value) { user_data.confirm.addCatapultImages = value; },
  4629.                                         editor: "bool"
  4630.                                     }
  4631.                                 }
  4632.                             }
  4633.                         });
  4634.                     }
  4635.                
  4636.                     if (showConfigs) {
  4637.                         user_data_configs.push({
  4638.                             id: "profile",
  4639.                             title: sangu_trans.profile.title,
  4640.                             save: sangu_saver,
  4641.                             properties: {
  4642.                                 show: {
  4643.                                     label: sangu_trans.global.resources.activate,
  4644.                                     propUI: {
  4645.                                         getter: function() { return user_data.profile.show; },
  4646.                                         setter: function(value) { user_data.profile.show = value; },
  4647.                                         editor: "bool"
  4648.                                     }
  4649.                                 },
  4650.                                 moveClaim: {
  4651.                                     label: sangu_trans.profile.moveClaim,
  4652.                                     propUI: {
  4653.                                         getter: function() { return user_data.profile.moveClaim; },
  4654.                                         setter: function(value) { user_data.profile.moveClaim = value; },
  4655.                                         editor: "bool"
  4656.                                     }
  4657.                                 },
  4658.                                 mapLink: {
  4659.                                     type: "subtitle",
  4660.                                     label: sangu_trans.profile.mapLink.title
  4661.                                 },
  4662.                                 mapLinkShow: {
  4663.                                     label: sangu_trans.global.resources.activate,
  4664.                                     propUI: {
  4665.                                         getter: function() { return user_data.profile.mapLink.show; },
  4666.                                         setter: function(value) { user_data.profile.mapLink.show = value; },
  4667.                                         editor: "bool"
  4668.                                     }
  4669.                                 },
  4670.                                 mapLinkFill: {
  4671.                                     label: sangu_trans.profile.mapLink.fill,
  4672.                                     propUI: {
  4673.                                         getter: function() { return user_data.profile.mapLink.fill; },
  4674.                                         setter: function(value) { user_data.profile.mapLink.fill = value; },
  4675.                                         editor: "color"
  4676.                                     }
  4677.                                 },
  4678.                                 mapLinkZoom: {
  4679.                                     label: sangu_trans.profile.mapLink.zoom,
  4680.                                     propUI: {
  4681.                                         getter: function() { return user_data.profile.mapLink.zoom; },
  4682.                                         setter: function(value) { user_data.profile.mapLink.zoom = value; },
  4683.                                         editor: "number|step=10"
  4684.                                     }
  4685.                                 },
  4686.                                 mapLinkGrid: {
  4687.                                     label: sangu_trans.profile.mapLink.grid,
  4688.                                     propUI: {
  4689.                                         getter: function() { return user_data.profile.mapLink.grid; },
  4690.                                         setter: function(value) { user_data.profile.mapLink.grid = value; },
  4691.                                         editor: "bool"
  4692.                                     }
  4693.                                 },
  4694.                                 mapLinkGridContinentNumbers: {
  4695.                                     label: sangu_trans.profile.mapLink.gridContinentNumbers,
  4696.                                     propUI: {
  4697.                                         getter: function() { return user_data.profile.mapLink.gridContinentNumbers; },
  4698.                                         setter: function(value) { user_data.profile.mapLink.gridContinentNumbers = value; },
  4699.                                         editor: "bool"
  4700.                                     }
  4701.                                 },
  4702.                                 mapLinkPlayerColor: {
  4703.                                     label: sangu_trans.profile.mapLink.playerColor,
  4704.                                     propUI: {
  4705.                                         getter: function() { return user_data.profile.mapLink.playerColor; },
  4706.                                         setter: function(value) { user_data.profile.mapLink.playerColor = value; },
  4707.                                         editor: "color"
  4708.                                     }
  4709.                                 },
  4710.                                 mapLinkTribeColor: {
  4711.                                     label: sangu_trans.profile.mapLink.tribeColor,
  4712.                                     propUI: {
  4713.                                         getter: function() { return user_data.profile.mapLink.tribeColor; },
  4714.                                         setter: function(value) { user_data.profile.mapLink.tribeColor = value; },
  4715.                                         editor: "color"
  4716.                                     }
  4717.                                 },
  4718.                                 mapLinkCentreX: {
  4719.                                     label: sangu_trans.profile.mapLink.centreX,
  4720.                                     propUI: {
  4721.                                         getter: function() { return user_data.profile.mapLink.centreX; },
  4722.                                         setter: function(value) { user_data.profile.mapLink.centreX = value; },
  4723.                                         editor: "number|step=10"
  4724.                                     }
  4725.                                 },
  4726.                                 mapLinkCentreY: {
  4727.                                     label: sangu_trans.profile.mapLink.centreY,
  4728.                                     propUI: {
  4729.                                         getter: function() { return user_data.profile.mapLink.centreY; },
  4730.                                         setter: function(value) { user_data.profile.mapLink.centreY = value; },
  4731.                                         editor: "number|step=10"
  4732.                                     }
  4733.                                 },
  4734.                                 mapLinkOwnColor: {
  4735.                                     label: sangu_trans.profile.mapLink.ownColor,
  4736.                                     propUI: {
  4737.                                         getter: function() { return user_data.profile.mapLink.ownColor; },
  4738.                                         setter: function(value) { user_data.profile.mapLink.ownColor = value; },
  4739.                                         editor: "color"
  4740.                                     }
  4741.                                 },
  4742.                                 mapLinkMarkedOnly: {
  4743.                                     label: sangu_trans.profile.mapLink.markedOnly,
  4744.                                     propUI: {
  4745.                                         getter: function() { return user_data.profile.mapLink.markedOnly; },
  4746.                                         setter: function(value) { user_data.profile.mapLink.markedOnly = value; },
  4747.                                         editor: "bool"
  4748.                                     }
  4749.                                 },
  4750.                                 mapLinkBigMarkers: {
  4751.                                     label: sangu_trans.profile.mapLink.bigMarkers,
  4752.                                     propUI: {
  4753.                                         getter: function() { return user_data.profile.mapLink.bigMarkers; },
  4754.                                         setter: function(value) { user_data.profile.mapLink.bigMarkers = value; },
  4755.                                         editor: "bool"
  4756.                                     }
  4757.                                 },
  4758.                                 mapLinkYourTribeColor: {
  4759.                                     label: sangu_trans.profile.mapLink.yourTribeColor,
  4760.                                     propUI: {
  4761.                                         getter: function() { return user_data.profile.mapLink.yourTribeColor; },
  4762.                                         setter: function(value) { user_data.profile.mapLink.yourTribeColor = value; },
  4763.                                         editor: "color"
  4764.                                     }
  4765.                                 },
  4766.                                 popup: {
  4767.                                     type: "subtitle",
  4768.                                     label: sangu_trans.profile.popup.title
  4769.                                 },
  4770.                                 popupShow: {
  4771.                                     label: sangu_trans.global.resources.activate,
  4772.                                     propUI: {
  4773.                                         getter: function() { return user_data.profile.popup.show; },
  4774.                                         setter: function(value) { user_data.profile.popup.show = value; },
  4775.                                         editor: "bool"
  4776.                                     }
  4777.                                 },
  4778.                                 popupTop: {
  4779.                                     label: sangu_trans.profile.popup.top,
  4780.                                     propUI: {
  4781.                                         getter: function() { return user_data.profile.popup.top; },
  4782.                                         setter: function(value) { user_data.profile.popup.top = value; },
  4783.                                         editor: "number|step=25"
  4784.                                     }
  4785.                                 },
  4786.                                 popupLeft: {
  4787.                                     label: sangu_trans.profile.popup.left,
  4788.                                     propUI: {
  4789.                                         getter: function() { return user_data.profile.popup.left; },
  4790.                                         setter: function(value) { user_data.profile.popup.left = value; },
  4791.                                         editor: "number|step=25"
  4792.                                     }
  4793.                                 },
  4794.                                 popupWidth: {
  4795.                                     label: sangu_trans.profile.popup.width,
  4796.                                     propUI: {
  4797.                                         getter: function() { return user_data.profile.popup.width; },
  4798.                                         setter: function(value) { user_data.profile.popup.width = value; },
  4799.                                         editor: "number|step=25"
  4800.                                     }
  4801.                                 },
  4802.                                 popupHeight: {
  4803.                                     label: sangu_trans.profile.popup.height,
  4804.                                     propUI: {
  4805.                                         getter: function() { return user_data.profile.popup.height; },
  4806.                                         setter: function(value) { user_data.profile.popup.height = value; },
  4807.                                         editor: "number|step=25"
  4808.                                     }
  4809.                                 }
  4810.                             }
  4811.                         });
  4812.                     }
  4813.                
  4814.                     if (showConfigs) {
  4815.                         user_data_configs.push({
  4816.                             id: "placeLinks",
  4817.                             title: sangu_trans.place.title,
  4818.                             save: sangu_saver,
  4819.                             properties: {
  4820.                                 scoutTitle: {
  4821.                                     type: "subtitle",
  4822.                                     label: sangu_trans.place.scoutTitle
  4823.                                 },
  4824.                                 scoutPlaceLinksName: {
  4825.                                     label: sangu_trans.place.linkText,
  4826.                                     propUI: {
  4827.                                         getter: function() { return user_data.place.attackLinks.scoutPlaceLinksName; },
  4828.                                         setter: function(value) { user_data.place.attackLinks.scoutPlaceLinksName = value; },
  4829.                                         editor: "text|width=23"
  4830.                                     }
  4831.                                 },
  4832.                                 scoutVillage: {
  4833.                                     label: sangu_trans.place.scoutVillage,
  4834.                                     propUI: {
  4835.                                         getter: function() { return user_data.place.attackLinks.scoutVillage; },
  4836.                                         setter: function(value) { user_data.place.attackLinks.scoutVillage = value; },
  4837.                                         editor: "number|step=10"
  4838.                                     }
  4839.                                 },
  4840.                                 scoutPlaceLinks: {
  4841.                                     label: sangu_trans.place.scoutPlaceLinks,
  4842.                                     propUI: {
  4843.                                         getter: function() { return user_data.place.attackLinks.scoutPlaceLinks; },
  4844.                                         setter: function(value) { user_data.place.attackLinks.scoutPlaceLinks = value; },
  4845.                                         editor: "array|addNew:number|step=10|delete"
  4846.                                     }
  4847.                                 },
  4848.                
  4849.                
  4850.                
  4851.                
  4852.                
  4853.                                 fakePlaceLinkTitle: {
  4854.                                     type: "subtitle",
  4855.                                     label: sangu_trans.place.fakePlaceLinkTitle
  4856.                                 },
  4857.                                 fakePlaceLinkName: {
  4858.                                     label: sangu_trans.place.linkText,
  4859.                                     propUI: {
  4860.                                         getter: function() { return user_data.place.attackLinks.fakePlaceLinkName; },
  4861.                                         setter: function(value) { user_data.place.attackLinks.fakePlaceLinkName = value; },
  4862.                                         editor: "text|width=23"
  4863.                                     }
  4864.                                 },
  4865.                                 fakePlaceLink: {
  4866.                                     label: sangu_trans.global.resources.activate,
  4867.                                     propUI: {
  4868.                                         getter: function() { return user_data.place.attackLinks.fakePlaceLink; },
  4869.                                         setter: function(value) { user_data.place.attackLinks.fakePlaceLink = value; },
  4870.                                         editor: "bool"
  4871.                                     }
  4872.                                 },
  4873.                                 fakePlaceExcludeTroops: {
  4874.                                     label: sangu_trans.place.fakePlaceExcludeTroops,
  4875.                                     tooltip: sangu_trans.place.fakePlaceExcludeTroopsTooltip,
  4876.                                     propUI: {
  4877.                                         getter: function() { return user_data.place.attackLinks.fakePlaceExcludeTroops; },
  4878.                                         setter: function(value) { user_data.place.attackLinks.fakePlaceExcludeTroops = value; },
  4879.                                         editor: "array|addNew:text|delete|width=7"
  4880.                                     }
  4881.                                 },
  4882.                
  4883.                
  4884.                
  4885.                
  4886.                
  4887.                
  4888.                                 noblePlaceLinkTitle: {
  4889.                                     type: "subtitle",
  4890.                                     label: sangu_trans.place.noblePlaceLinkTitle
  4891.                                 },
  4892.                                 noblePlaceLinkDivideTitle: {
  4893.                                     type: "subtitle",
  4894.                                     label: sangu_trans.place.noblePlaceLinkDivideTitle
  4895.                                 },
  4896.                                 noblePlaceLinkDivideName: {
  4897.                                     label: sangu_trans.place.linkText,
  4898.                                     propUI: {
  4899.                                         getter: function() { return user_data.place.attackLinks.noblePlaceLinkDivideName; },
  4900.                                         setter: function(value) { user_data.place.attackLinks.noblePlaceLinkDivideName = value; },
  4901.                                         editor: "text|width=23"
  4902.                                     }
  4903.                                 },
  4904.                                 noblePlaceLink: {
  4905.                                     label: sangu_trans.global.resources.activate,
  4906.                                     propUI: {
  4907.                                         getter: function() { return user_data.place.attackLinks.noblePlaceLink; },
  4908.                                         setter: function(value) { user_data.place.attackLinks.noblePlaceLink = value; },
  4909.                                         editor: "bool"
  4910.                                     }
  4911.                                 },
  4912.                                 noblePlaceLinkDivideAddRam: {
  4913.                                     label: sangu_trans.place.noblePlaceLinkDivideAddRam,
  4914.                                     propUI: {
  4915.                                         getter: function() { return user_data.place.attackLinks.noblePlaceLinkDivideAddRam; },
  4916.                                         setter: function(value) { user_data.place.attackLinks.noblePlaceLinkDivideAddRam = value; },
  4917.                                         editor: "bool"
  4918.                                     }
  4919.                                 },
  4920.                                 noblePlaceLinkFirstTitle: {
  4921.                                     type: "subtitle",
  4922.                                     label: sangu_trans.place.noblePlaceLinkFirstTitle
  4923.                                 },
  4924.                                 noblePlaceLinkFirstName: {
  4925.                                     label: sangu_trans.place.linkText,
  4926.                                     tooltip: sangu_trans.place.noblePlaceLinkFirstNameTooltip,
  4927.                                     propUI: {
  4928.                                         getter: function() { return user_data.place.attackLinks.noblePlaceLinkFirstName; },
  4929.                                         setter: function(value) { user_data.place.attackLinks.noblePlaceLinkFirstName = value; },
  4930.                                         editor: "text|width=23"
  4931.                                     }
  4932.                                 },
  4933.                                 noblePlaceLinkSupportTitle: {
  4934.                                     type: "subtitle",
  4935.                                     label: sangu_trans.place.noblePlaceLinkSupportTitle
  4936.                                 },
  4937.                                 noblePlaceLinkSupportName: {
  4938.                                     label: sangu_trans.place.linkText,
  4939.                                     propUI: {
  4940.                                         getter: function() { return user_data.place.attackLinks.noblePlaceLinkSupportName; },
  4941.                                         setter: function(value) { user_data.place.attackLinks.noblePlaceLinkSupportName = value; },
  4942.                                         editor: "text|width=23"
  4943.                                     }
  4944.                                 },
  4945.                                 noblePlaceLinksForceShow: {
  4946.                                     label: sangu_trans.place.noblePlaceLinksForceShow,
  4947.                                     propUI: {
  4948.                                         getter: function() { return user_data.place.attackLinks.noblePlaceLinksForceShow; },
  4949.                                         setter: function(value) { user_data.place.attackLinks.noblePlaceLinksForceShow = value; },
  4950.                                         editor: "bool"
  4951.                                     }
  4952.                                 },
  4953.                                 nobleSupportOffTitle: {
  4954.                                     type: "subtitle",
  4955.                                     label: sangu_trans.place.nobleSupportOffTitle
  4956.                                 },
  4957.                                 nobleSupportOffUnit: {
  4958.                                     label: sangu_trans.place.nobleSupportUnit,
  4959.                                     propUI: {
  4960.                                         getter: function() { return user_data.place.attackLinks.nobleSupport[0].unit; },
  4961.                                         setter: function(value) { user_data.place.attackLinks.nobleSupport[0].unit = value; },
  4962.                                         editor: "unit"
  4963.                                     }
  4964.                                 },
  4965.                                 nobleSupportOffAmount: {
  4966.                                     label: sangu_trans.place.nobleSupportAmount,
  4967.                                     propUI: {
  4968.                                         getter: function() { return user_data.place.attackLinks.nobleSupport[0].amount; },
  4969.                                         setter: function(value) { user_data.place.attackLinks.nobleSupport[0].amount = value; },
  4970.                                         editor: "number|step=50"
  4971.                                     }
  4972.                                 },
  4973.                                 nobleSupportDefTitle: {
  4974.                                     type: "subtitle",
  4975.                                     label: sangu_trans.place.nobleSupportDefTitle
  4976.                                 },
  4977.                                 nobleSupportDefUnit: {
  4978.                                     label: sangu_trans.place.nobleSupportUnit,
  4979.                                     propUI: {
  4980.                                         getter: function() { return user_data.place.attackLinks.nobleSupport[1].unit; },
  4981.                                         setter: function(value) { user_data.place.attackLinks.nobleSupport[1].unit = value; },
  4982.                                         editor: "unit"
  4983.                                     }
  4984.                                 },
  4985.                                 nobleSupportDefAmount: {
  4986.                                     label: sangu_trans.place.nobleSupportAmount,
  4987.                                     propUI: {
  4988.                                         getter: function() { return user_data.place.attackLinks.nobleSupport[1].amount; },
  4989.                                         setter: function(value) { user_data.place.attackLinks.nobleSupport[1].amount = value; },
  4990.                                         editor: "number|step=50"
  4991.                                     }
  4992.                                 }
  4993.                             }
  4994.                         });
  4995.                     }
  4996.                
  4997.                     if (showConfigs) {
  4998.                         (function() {
  4999.                             var i,
  5000.                                 properties = {};
  5001.                
  5002.                             for (i = 0; i < user_data.place.customPlaceLinks.length; i++) {
  5003.                                 (function() {
  5004.                                     var unitTypeIndex,
  5005.                                         customPlaceLink = user_data.place.customPlaceLinks[i],
  5006.                                         oneTimeTooltip = i == 0 ? sangu_trans.place.customPlaceOneTimeTooltip : undefined,
  5007.                                         oneTimeTooltipSendAlong = i == 0 ? sangu_trans.place.customPlaceSendAlongTooltip : undefined;
  5008.                
  5009.                                     properties['customPlaceLink'+i+'Title'] = {
  5010.                                         type: "subtitle",
  5011.                                         label: sangu_trans.place.link.replace("{name}", customPlaceLink.name)
  5012.                                     };
  5013.                
  5014.                                     properties['customPlaceLink'+i+'Name'] = {
  5015.                                         label: sangu_trans.place.linkText,
  5016.                                         propUI: {
  5017.                                             getter: function() { return customPlaceLink.name; },
  5018.                                             setter: function(value) { customPlaceLink.name = value; },
  5019.                                             editor: "text|width=23"
  5020.                                         }
  5021.                                     };
  5022.                
  5023.                                     properties['customPlaceLink'+i+'Active'] = {
  5024.                                         label: sangu_trans.global.resources.activate,
  5025.                                         propUI: {
  5026.                                             getter: function() { return customPlaceLink.active; },
  5027.                                             setter: function(value) { customPlaceLink.active = value; },
  5028.                                             editor: "bool"
  5029.                                         }
  5030.                                     };
  5031.                
  5032.                                     for (unitTypeIndex = 0; unitTypeIndex < world_data.units.length; unitTypeIndex++) {
  5033.                                         (function() {
  5034.                                             var unit = world_data.units[unitTypeIndex],
  5035.                                                 reallyOneTimeTooltip = unitTypeIndex == 0 && oneTimeTooltip ? oneTimeTooltip : undefined;
  5036.                
  5037.                                             properties['customPlaceLink'+i+unit] = {
  5038.                                                 label: "<img src='graphic/unit/unit_"+unit+".png'/>",
  5039.                                                 tooltip: reallyOneTimeTooltip,
  5040.                                                 propUI: {
  5041.                                                     getter: function() { return customPlaceLink[unit]; },
  5042.                                                     setter: function(value) { customPlaceLink[unit] = value; },
  5043.                                                     editor: "number|step=100"
  5044.                                                 }
  5045.                                             };
  5046.                                         })();
  5047.                                     }
  5048.                
  5049.                                     properties['customPlaceLink'+i+'SendAlong'] = {
  5050.                                         label: sangu_trans.place.customPlaceSendAlong,
  5051.                                         tooltip: oneTimeTooltipSendAlong,
  5052.                                         propUI: {
  5053.                                             getter: function() { return customPlaceLink.sendAlong; },
  5054.                                             setter: function(value) { customPlaceLink.sendAlong = value; },
  5055.                                             editor: "number|step=100"
  5056.                                         }
  5057.                                     };
  5058.                                 })();
  5059.                             }
  5060.                
  5061.                             user_data_configs.push({
  5062.                                 id: "placeLinksCustom",
  5063.                                 title: sangu_trans.place.titleCustom,
  5064.                                 save: sangu_saver,
  5065.                                 properties: properties
  5066.                             });
  5067.                         }());
  5068.                     }
  5069.                
  5070.                     if (showConfigs) {
  5071.                         user_data_configs.push({
  5072.                             id: "overviewsTroops",
  5073.                             title: sangu_trans.overviews.command.title,
  5074.                             save: sangu_saver,
  5075.                             properties: {
  5076.                                 changeTroopsOverviewLink: {
  5077.                                     label: sangu_trans.overviews.command.changeTroopsOverviewLink,
  5078.                                     propUI: {
  5079.                                         getter: function() { return user_data.command.changeTroopsOverviewLink; },
  5080.                                         setter: function(value) { user_data.command.changeTroopsOverviewLink = value; },
  5081.                                         editor: "bool"
  5082.                                     }
  5083.                                 },
  5084.                                 defaultPopulationFilterAmount: {
  5085.                                     label: sangu_trans.overviews.troopsRestack.defaultPopulationFilterAmount,
  5086.                                     propUI: {
  5087.                                         getter: function() { return user_data.restack.defaultPopulationFilterAmount; },
  5088.                                         setter: function(value) { user_data.restack.defaultPopulationFilterAmount = value; },
  5089.                                         editor: "number|step=1000"
  5090.                                     }
  5091.                                 },
  5092.                
  5093.                                 titleOwnTroopsPage: {
  5094.                                     type: "subtitle",
  5095.                                     label: sangu_trans.overviews.command.titleOwnTroopsPage
  5096.                                 },
  5097.                                 middleMouseClickDeletesRow: {
  5098.                                     label: sangu_trans.overviews.command.middleMouseClickDeletesRow,
  5099.                                     tooltip: sangu_trans.overviews.command.middleMouseClickDeletesRowTooltip,
  5100.                                     propUI: {
  5101.                                         getter: function() { return user_data.command.middleMouseClickDeletesRow2; },
  5102.                                         setter: function(value) { user_data.command.middleMouseClickDeletesRow2 = value; },
  5103.                                         editor: "bool"
  5104.                                     }
  5105.                                 },
  5106.                                 filterAutoSort: {
  5107.                                     label: sangu_trans.overviews.command.filterAutoSort,
  5108.                                     propUI: {
  5109.                                         getter: function() { return user_data.command.filterAutoSort; },
  5110.                                         setter: function(value) { user_data.command.filterAutoSort = value; },
  5111.                                         editor: "bool"
  5112.                                     }
  5113.                                 },
  5114.                
  5115.                
  5116.                                 titleDefensePage: {
  5117.                                     type: "subtitle",
  5118.                                     label: sangu_trans.overviews.command.titleDefensePage
  5119.                                 },
  5120.                                 fieldsDistanceFilterDefault: {
  5121.                                     label: sangu_trans.overviews.troopsRestack.fieldsDistanceFilterDefault,
  5122.                                     propUI: {
  5123.                                         getter: function() { return user_data.restack.fieldsDistanceFilterDefault; },
  5124.                                         setter: function(value) { user_data.restack.fieldsDistanceFilterDefault = value; },
  5125.                                         editor: "number"
  5126.                                     }
  5127.                                 },
  5128.                                 filterReverse: {
  5129.                                     label: sangu_trans.overviews.troopsRestack.filterReverse,
  5130.                                     tooltip: sangu_trans.overviews.troopsRestack.filterReverseTooltip,
  5131.                                     propUI: {
  5132.                                         getter: function() { return user_data.restack.filterReverse; },
  5133.                                         setter: function(value) { user_data.restack.filterReverse = value; },
  5134.                                         editor: "bool"
  5135.                                     }
  5136.                                 },
  5137.                                 filterMinPopulation: {
  5138.                                     label: sangu_trans.overviews.command.filterMinPopulation,
  5139.                                     propUI: {
  5140.                                         getter: function() { return user_data.command.filterMinPopulation; },
  5141.                                         setter: function(value) { user_data.command.filterMinPopulation = value; },
  5142.                                         editor: "number|step=1000"
  5143.                                     }
  5144.                                 },
  5145.                                 removeRowsWithoutSupport: {
  5146.                                     label: sangu_trans.overviews.troopsRestack.removeRowsWithoutSupport,
  5147.                                     propUI: {
  5148.                                         getter: function() { return user_data.restack.removeRowsWithoutSupport; },
  5149.                                         setter: function(value) { user_data.restack.removeRowsWithoutSupport = value; },
  5150.                                         editor: "bool"
  5151.                                     }
  5152.                                 },
  5153.                                 autohideWithoutSupportAfterFilter: {
  5154.                                     label: sangu_trans.overviews.troopsRestack.autohideWithoutSupportAfterFilter,
  5155.                                     tooltip: sangu_trans.overviews.troopsRestack.autohideWithoutSupportAfterFilterTooltip,
  5156.                                     propUI: {
  5157.                                         getter: function() { return user_data.restack.autohideWithoutSupportAfterFilter; },
  5158.                                         setter: function(value) { user_data.restack.autohideWithoutSupportAfterFilter = value; },
  5159.                                         editor: "bool"
  5160.                                     }
  5161.                                 },
  5162.                                 calculateDefTotalsAfterFilter: {
  5163.                                     label: sangu_trans.overviews.troopsRestack.calculateDefTotalsAfterFilter,
  5164.                                     tooltip: sangu_trans.overviews.troopsRestack.calculateDefTotalsAfterFilterTooltip,
  5165.                                     propUI: {
  5166.                                         getter: function() { return user_data.restack.calculateDefTotalsAfterFilter; },
  5167.                                         setter: function(value) { user_data.restack.calculateDefTotalsAfterFilter = value; },
  5168.                                         editor: "bool"
  5169.                                     }
  5170.                                 },
  5171.                
  5172.                                 commandTitle: {
  5173.                                     type: "subtitle",
  5174.                                     label: sangu_trans.overviews.command.filterOnUnitTypeSeperator
  5175.                                 },
  5176.                                 filterMinDefaultType: {
  5177.                                     label: sangu_trans.overviews.command.filterMinDefaultType,
  5178.                                     propUI: {
  5179.                                         getter: function() { return user_data.command.filterMinDefaultType; },
  5180.                                         setter: function(value) { user_data.command.filterMinDefaultType = value; },
  5181.                                         editor: "unit"
  5182.                                     }
  5183.                                 },
  5184.                                 filterMinDefault: {
  5185.                                     label: sangu_trans.overviews.command.filterMinDefault,
  5186.                                     tooltip: sangu_trans.overviews.command.filterMinDefaultTooltip,
  5187.                                     propUI: {
  5188.                                         getter: function() { return user_data.command.filterMinDefault; },
  5189.                                         setter: function(value) { user_data.command.filterMinDefault = value; },
  5190.                                         editor: "number|step=100"
  5191.                                     }
  5192.                                 },
  5193.                                 filterMinSpear: {
  5194.                                     label: "<img src='graphic/unit/unit_spear.png' />",
  5195.                                     propUI: {
  5196.                                         getter: function() { return user_data.command.filterMin.spear; },
  5197.                                         setter: function(value) { user_data.command.filterMin.spear = value; },
  5198.                                         editor: "number|step=500"
  5199.                                     }
  5200.                                 },
  5201.                                 filterMinSword: {
  5202.                                     label: "<img src='graphic/unit/unit_sword.png' />",
  5203.                                     propUI: {
  5204.                                         getter: function() { return user_data.command.filterMin.sword; },
  5205.                                         setter: function(value) { user_data.command.filterMin.sword = value; },
  5206.                                         editor: "number|step=500"
  5207.                                     }
  5208.                                 },
  5209.                                 filterMinAxe: {
  5210.                                     label: "<img src='graphic/unit/unit_axe.png' />",
  5211.                                     propUI: {
  5212.                                         getter: function() { return user_data.command.filterMin.axe; },
  5213.                                         setter: function(value) { user_data.command.filterMin.axe = value; },
  5214.                                         editor: "number|step=500"
  5215.                                     }
  5216.                                 },
  5217.                                 filterMinArcher: {
  5218.                                     label: "<img src='graphic/unit/unit_archer.png' />",
  5219.                                     show: world_config.hasArchers,
  5220.                                     propUI: {
  5221.                                         getter: function() { return user_data.command.filterMin.archer; },
  5222.                                         setter: function(value) { user_data.command.filterMin.archer = value; },
  5223.                                         editor: "number|step=500"
  5224.                                     }
  5225.                                 },
  5226.                                 filterMinSpy: {
  5227.                                     label: "<img src='graphic/unit/unit_spy.png' />",
  5228.                                     propUI: {
  5229.                                         getter: function() { return user_data.command.filterMin.spy; },
  5230.                                         setter: function(value) { user_data.command.filterMin.spy = value; },
  5231.                                         editor: "number|step=100"
  5232.                                     }
  5233.                                 },
  5234.                                 filterMinLight: {
  5235.                                     label: "<img src='graphic/unit/unit_light.png' />",
  5236.                                     propUI: {
  5237.                                         getter: function() { return user_data.command.filterMin.light; },
  5238.                                         setter: function(value) { user_data.command.filterMin.light = value; },
  5239.                                         editor: "number|step=100"
  5240.                                     }
  5241.                                 },
  5242.                                 filterMinMarcher: {
  5243.                                     label: "<img src='graphic/unit/unit_marcher.png' />",
  5244.                                     show: world_config.hasArchers,
  5245.                                     propUI: {
  5246.                                         getter: function() { return user_data.command.filterMin.marcher; },
  5247.                                         setter: function(value) { user_data.command.filterMin.marcher = value; },
  5248.                                         editor: "number|step=100"
  5249.                                     }
  5250.                                 },
  5251.                                 filterMinHeavy: {
  5252.                                     label: "<img src='graphic/unit/unit_heavy.png' />",
  5253.                                     propUI: {
  5254.                                         getter: function() { return user_data.command.filterMin.heavy; },
  5255.                                         setter: function(value) { user_data.command.filterMin.heavy = value; },
  5256.                                         editor: "number|step=100"
  5257.                                     }
  5258.                                 },
  5259.                                 filterMinRam: {
  5260.                                     label: "<img src='graphic/unit/unit_ram.png' />",
  5261.                                     propUI: {
  5262.                                         getter: function() { return user_data.command.filterMin.ram; },
  5263.                                         setter: function(value) { user_data.command.filterMin.ram = value; },
  5264.                                         editor: "number|step=10"
  5265.                                     }
  5266.                                 },
  5267.                                 filterMinCatapult: {
  5268.                                     label: "<img src='graphic/unit/unit_catapult.png' />",
  5269.                                     propUI: {
  5270.                                         getter: function() { return user_data.command.filterMin.catapult; },
  5271.                                         setter: function(value) { user_data.command.filterMin.catapult = value; },
  5272.                                         editor: "number|step=10"
  5273.                                     }
  5274.                                 },
  5275.                                 filterMinSnob: {
  5276.                                     label: "<img src='graphic/unit/unit_snob.png' />",
  5277.                                     propUI: {
  5278.                                         getter: function() { return user_data.command.filterMin.snob; },
  5279.                                         setter: function(value) { user_data.command.filterMin.snob = value; },
  5280.                                         editor: "number"
  5281.                                     }
  5282.                                 },
  5283.                                 filterMinOther: {
  5284.                                     label: sangu_trans.overviews.command.filterMinOther,
  5285.                                     propUI: {
  5286.                                         getter: function() { return user_data.command.filterMinOther; },
  5287.                                         setter: function(value) { user_data.command.filterMinOther = value; },
  5288.                                         editor: "number|step=500"
  5289.                                     }
  5290.                                 },
  5291.                                 restackTitle: {
  5292.                                     type: "subtitle",
  5293.                                     label: sangu_trans.overviews.troopsRestack.title
  5294.                                 },
  5295.                                 troopsRestackTo: {
  5296.                                     label: sangu_trans.overviews.troopsRestack.to,
  5297.                                     propUI: {
  5298.                                         getter: function() { return user_data.restack.to; },
  5299.                                         setter: function(value) { user_data.restack.to = value; },
  5300.                                         editor: "number|step=1000"
  5301.                                     }
  5302.                                 },
  5303.                                 requiredDifference: {
  5304.                                     label: sangu_trans.overviews.troopsRestack.requiredDifference,
  5305.                                     propUI: {
  5306.                                         getter: function() { return user_data.restack.requiredDifference; },
  5307.                                         setter: function(value) { user_data.restack.requiredDifference = value; },
  5308.                                         editor: "number|step=500"
  5309.                                     }
  5310.                                 }
  5311.                             }
  5312.                         });
  5313.                     }
  5314.                
  5315.                     if (showConfigs) {
  5316.                         user_data_configs.push({
  5317.                             id: "overviewsCommands",
  5318.                             title: sangu_trans.overviews.commands.title,
  5319.                             save: sangu_saver,
  5320.                             properties: {
  5321.                                 sumRow: {
  5322.                                     label: sangu_trans.overviews.commands.sumRow,
  5323.                                     propUI: {
  5324.                                         getter: function() { return user_data.command.sumRow; },
  5325.                                         setter: function(value) { user_data.command.sumRow = value; },
  5326.                                         editor: "bool"
  5327.                                     }
  5328.                                 },
  5329.                                 filterFakeMaxPop: {
  5330.                                     label: sangu_trans.overviews.commands.filterFakeMaxPop,
  5331.                                     propUI: {
  5332.                                         getter: function() { return user_data.command.filterFakeMaxPop; },
  5333.                                         setter: function(value) { user_data.command.filterFakeMaxPop = value; },
  5334.                                         editor: "number|step=100"
  5335.                                     }
  5336.                                 },
  5337.                                 requiredTroopAmount: {
  5338.                                     label: sangu_trans.overviews.commands.requiredTroopAmount,
  5339.                                     propUI: {
  5340.                                         getter: function() { return user_data.command.bbCodeExport.requiredTroopAmount; },
  5341.                                         setter: function(value) { user_data.command.bbCodeExport.requiredTroopAmount = value; },
  5342.                                         editor: "number|step=100"
  5343.                                     }
  5344.                                 }
  5345.                             }
  5346.                         });
  5347.                     }
  5348.                
  5349.                     if (showConfigs) {
  5350.                         user_data_configs.push({
  5351.                             id: "overviewsResources",
  5352.                             title: sangu_trans.overviews.resources.title,
  5353.                             save: sangu_saver,
  5354.                             properties: {
  5355.                                 requiredResDefault: {
  5356.                                     label: sangu_trans.overviews.resources.requiredResDefault,
  5357.                                     propUI: {
  5358.                                         getter: function() { return user_data.resources.requiredResDefault; },
  5359.                                         setter: function(value) { user_data.resources.requiredResDefault = value; },
  5360.                                         editor: "number|step=10000"
  5361.                                     }
  5362.                                 },
  5363.                                 requiredMerchants: {
  5364.                                     label: sangu_trans.overviews.resources.requiredMerchants,
  5365.                                     propUI: {
  5366.                                         getter: function() { return user_data.resources.requiredMerchants; },
  5367.                                         setter: function(value) { user_data.resources.requiredMerchants = value; },
  5368.                                         editor: "number|step=10"
  5369.                                     }
  5370.                                 },
  5371.                                 filterMerchants: {
  5372.                                     label: sangu_trans.overviews.resources.filterMerchants,
  5373.                                     propUI: {
  5374.                                         getter: function() { return user_data.resources.filterMerchants; },
  5375.                                         setter: function(value) { user_data.resources.filterMerchants = value; },
  5376.                                         editor: "bool"
  5377.                                     }
  5378.                                 },
  5379.                                 highlightColor: {
  5380.                                     label: sangu_trans.overviews.resources.highlightColor,
  5381.                                     propUI: {
  5382.                                         getter: function() { return user_data.resources.highlightColor; },
  5383.                                         setter: function(value) { user_data.resources.highlightColor = value; },
  5384.                                         editor: "color"
  5385.                                     }
  5386.                                 },
  5387.                                 filterRows: {
  5388.                                     label: sangu_trans.overviews.resources.filterRows,
  5389.                                     propUI: {
  5390.                                         getter: function() { return user_data.resources.filterRows; },
  5391.                                         setter: function(value) { user_data.resources.filterRows = value; },
  5392.                                         editor: "bool"
  5393.                                     }
  5394.                                 },
  5395.                                 bbcodeMinimumDiff: {
  5396.                                     label: sangu_trans.overviews.resources.bbcodeMinimumDiff,
  5397.                                     propUI: {
  5398.                                         getter: function() { return user_data.resources.bbcodeMinimumDiff; },
  5399.                                         setter: function(value) { user_data.resources.bbcodeMinimumDiff = value; },
  5400.                                         editor: "number|step=2000"
  5401.                                     }
  5402.                                 }
  5403.                             }
  5404.                         });
  5405.                     }
  5406.                
  5407.                     if (showConfigs) {
  5408.                         // Buildingsoverview:
  5409.                         (function() {
  5410.                             var properties = {},
  5411.                                 i;
  5412.                
  5413.                             for (i = 0; i < world_data.buildings.length; i++) {
  5414.                                 (function() {
  5415.                                     var captured_index = i,
  5416.                                         building_name = world_data.buildings[captured_index],
  5417.                                         buildingPrettyfier = function(building) { return "<img src='graphic/buildings/"+building+".png'>"; };
  5418.                
  5419.                                     properties[building_name+'_min'] = {
  5420.                                         label: sangu_trans.overviews.buildings.minLevel.replace("{building}", buildingPrettyfier(building_name)),
  5421.                                         propUI: {
  5422.                                             getter: function() { return user_data.buildings[building_name][0]; },
  5423.                                             setter: function(value) { user_data.buildings[building_name][0] = value; },
  5424.                                             editor: "number"
  5425.                                         }
  5426.                                     };
  5427.                
  5428.                                     properties[building_name+'_max'] = {
  5429.                                         label: sangu_trans.overviews.buildings.maxLevel.replace("{building}", buildingPrettyfier(building_name)),
  5430.                                         propUI: {
  5431.                                             getter: function() { return user_data.buildings[building_name][1]; },
  5432.                                             setter: function(value) { user_data.buildings[building_name][1] = value; },
  5433.                                             editor: "number"
  5434.                                         }
  5435.                                     };
  5436.                                 })();
  5437.                             }
  5438.                
  5439.                             user_data_configs.push({
  5440.                                 id: "overviewsBuildings",
  5441.                                 title: sangu_trans.overviews.buildings.title,
  5442.                                 save: sangu_saver,
  5443.                                 properties: properties
  5444.                             });
  5445.                         }());
  5446.                     }
  5447.                
  5448.                     if (showConfigs) {
  5449.                         // Incomingsoverview:
  5450.                         (function() {
  5451.                             var properties = {},
  5452.                                 i;
  5453.                
  5454.                             properties.attackIdTitle = {
  5455.                                 type: "subtitle",
  5456.                                     label: sangu_trans.overviews.incomings.attackIdTitle
  5457.                             };
  5458.                
  5459.                             for (i = 0; i < user_data.incomings.attackIdDescriptions.length; i++) {
  5460.                                 (function() {
  5461.                                     var captured_index = i;
  5462.                
  5463.                                     properties['incomings_attackIdDesc'+captured_index+'_title'] = {
  5464.                                         type: "subtitle",
  5465.                                             label: sangu_trans.overviews.incomings.seperatorTitle.replace("{minValue}", user_data.incomings.attackIdDescriptions[captured_index].minValue)
  5466.                                     };
  5467.                
  5468.                                     properties['incomings_attackIdDesc'+captured_index+'_minValue'] = {
  5469.                                         label: sangu_trans.overviews.incomings.minValue,
  5470.                                         tooltip: captured_index == 0 ? sangu_trans.overviews.incomings.minValueTooltip : undefined,
  5471.                                         propUI: {
  5472.                                             getter: function() { return user_data.incomings.attackIdDescriptions[captured_index].minValue; },
  5473.                                             setter: function(value) { user_data.incomings.attackIdDescriptions[captured_index].minValue = value; },
  5474.                                             editor: "number"
  5475.                                         }
  5476.                                     };
  5477.                
  5478.                                     properties['incomings_attackIdDesc'+captured_index+'_text'] = {
  5479.                                         label: sangu_trans.overviews.incomings.text,
  5480.                                         propUI: {
  5481.                                             getter: function() { return user_data.incomings.attackIdDescriptions[captured_index].text; },
  5482.                                             setter: function(value) { user_data.incomings.attackIdDescriptions[captured_index].text = value; },
  5483.                                             editor: "text"
  5484.                                         }
  5485.                                     };
  5486.                                 })();
  5487.                             }
  5488.                
  5489.                             properties['incomings_attackIdDescMaxText'] = {
  5490.                                 label: sangu_trans.overviews.incomings.attackIdHigherDescription,
  5491.                                 propUI: {
  5492.                                     getter: function() { return user_data.incomings.attackIdHigherDescription; },
  5493.                                     setter: function(value) { user_data.incomings.attackIdHigherDescription = value; },
  5494.                                     editor: "text"
  5495.                                 }
  5496.                             };
  5497.                
  5498.                             user_data_configs.push({
  5499.                                 id: "overviewsIncomings",
  5500.                                 title: sangu_trans.overviews.incomings.title,
  5501.                                 save: sangu_saver,
  5502.                                 properties: properties
  5503.                             });
  5504.                         }());
  5505.                     }
  5506.                
  5507.                     if (showConfigs) {
  5508.                         user_data_configs.push({
  5509.                             id: "other",
  5510.                             title: sangu_trans.other.title,
  5511.                             save: sangu_saver,
  5512.                             properties: {
  5513.                                 fancyImages: {
  5514.                                     label: sangu_trans.overviews.addFancyImagesToOverviewLinks,
  5515.                                     propUI: {
  5516.                                         getter: function() { return user_data.overviews.addFancyImagesToOverviewLinks; },
  5517.                                         setter: function(value) { user_data.overviews.addFancyImagesToOverviewLinks = value; },
  5518.                                         editor: "bool"
  5519.                                     }
  5520.                                 },
  5521.                                 proStyle: {
  5522.                                     tooltip: sangu_trans.other.proStyleTooltip,
  5523.                                     label: sangu_trans.other.proStyle,
  5524.                                     propUI: {
  5525.                                         getter: function() { return user_data.proStyle; },
  5526.                                         setter: function(value) { user_data.proStyle = value; },
  5527.                                         editor: "bool"
  5528.                                     }
  5529.                                 },
  5530.                                 calculateSnob: {
  5531.                                     label: sangu_trans.other.calculateSnob,
  5532.                                     show: !world_config.coins,
  5533.                                     propUI: {
  5534.                                         getter: function() { return user_data.other.calculateSnob; },
  5535.                                         setter: function(value) { user_data.other.calculateSnob = value; },
  5536.                                         editor: "bool"
  5537.                                     }
  5538.                                 },
  5539.                                 showPlayerProfileOnVillage: {
  5540.                                     label: sangu_trans.other.showPlayerProfileOnVillage,
  5541.                                     propUI: {
  5542.                                         getter: function() { return user_data.showPlayerProfileOnVillage; },
  5543.                                         setter: function(value) { user_data.showPlayerProfileOnVillage = value; },
  5544.                                         editor: "bool"
  5545.                                     }
  5546.                                 },
  5547.                                 overviewAjaxSeperateSupport: {
  5548.                                     label: sangu_trans.other.ajaxSeperateSupport,
  5549.                                     show: server_settings.ajaxAllowed,
  5550.                                     propUI: {
  5551.                                         getter: function() { return user_data.overview.ajaxSeperateSupport; },
  5552.                                         setter: function(value) { user_data.overview.ajaxSeperateSupport = value; },
  5553.                                         editor: "bool"
  5554.                                     }
  5555.                                 },
  5556.                                 canHideDiv: {
  5557.                                     label: sangu_trans.other.canHideDiv,
  5558.                                     propUI: {
  5559.                                         getter: function() { return user_data.overview.canHideDiv; },
  5560.                                         setter: function(value) { user_data.overview.canHideDiv = value; },
  5561.                                         editor: "bool"
  5562.                                     }
  5563.                                 },
  5564.                                 timeDisplayTitle: {
  5565.                                     type: "subtitle",
  5566.                                     label: sangu_trans.other.timeDisplayTitle
  5567.                                 },
  5568.                                 walkingTimeDisplay: {
  5569.                                     label: sangu_trans.other.walkingTimeDisplay,
  5570.                                     tooltip: sangu_trans.other.walkingTimeDisplayTooltip,
  5571.                                     propUI: {
  5572.                                         getter: function() { return user_data.walkingTimeDisplay; },
  5573.                                         setter: function(value) { user_data.walkingTimeDisplay = value; },
  5574.                                         editor: "text"
  5575.                                     }
  5576.                                 },
  5577.                                 displayDays: {
  5578.                                     label: sangu_trans.other.displayDays,
  5579.                                     tooltip: sangu_trans.other.displayDaysTooltip,
  5580.                                     propUI: {
  5581.                                         getter: function() { return user_data.displayDays; },
  5582.                                         setter: function(value) { user_data.displayDays = value; },
  5583.                                         editor: "bool"
  5584.                                     }
  5585.                                 },
  5586.                                 commandRenamerTitle: {
  5587.                                     type: "subtitle",
  5588.                                     label: sangu_trans.other.commandRenamer
  5589.                                 },
  5590.                                 commandRenamerActive: {
  5591.                                     label: sangu_trans.other.commandRenamerActive,
  5592.                                     propUI: {
  5593.                                         getter: function() { return user_data.attackAutoRename.active; },
  5594.                                         setter: function(value) { user_data.attackAutoRename.active = value; },
  5595.                                         editor: "bool"
  5596.                                     }
  5597.                                 },
  5598.                                 commandRenamerAddHaul: {
  5599.                                     label: sangu_trans.other.commandRenamerAddHaul,
  5600.                                     propUI: {
  5601.                                         getter: function() { return user_data.attackAutoRename.addHaul; },
  5602.                                         setter: function(value) { user_data.attackAutoRename.addHaul = value; },
  5603.                                         editor: "bool"
  5604.                                     }
  5605.                                 },
  5606.                                 farmLimitTitle: {
  5607.                                     type: "subtitle",
  5608.                                     label: sangu_trans.other.farmLimitTitle
  5609.                                 },
  5610.                                 farmLimitColors: {
  5611.                                     label: sangu_trans.other.farmLimitStackColors,
  5612.                                     propUI: {
  5613.                                         getter: function(propIndex) {
  5614.                                             return user_data.farmLimit.stackColors;
  5615.                                         },
  5616.                                         setter: function(value, propIndex) { user_data.farmLimit.stackColors = value; },
  5617.                                         editor: "array|addNew:color|delete"
  5618.                                     }
  5619.                                 },
  5620.                                 farmLimitAcceptableOverstack: {
  5621.                                     label: sangu_trans.other.farmLimitAcceptableOverstack,
  5622.                                     tooltip: sangu_trans.other.farmLimitAcceptableOverstackTooltip.replace("{farmlimit}", 30 * world_config.farmLimit),
  5623.                                     show: world_config.farmLimit,
  5624.                                     propUI: {
  5625.                                         getter: function() { return user_data.farmLimit.acceptableOverstack; },
  5626.                                         setter: function(value) { user_data.farmLimit.acceptableOverstack = value; },
  5627.                                         editor: "array|addNew:float|delete|step=0.01"
  5628.                                     }
  5629.                                 },
  5630.                                 farmLimitUnlimitedStack: {
  5631.                                     label: sangu_trans.other.farmLimitUnlimitedStack,
  5632.                                     show: !world_config.farmLimit,
  5633.                                     propUI: {
  5634.                                         getter: function() { return user_data.farmLimit.unlimitedStack; },
  5635.                                         setter: function(value) { user_data.farmLimit.unlimitedStack = value; },
  5636.                                         editor: "array|addNew:number|delete|step=1000"
  5637.                                     }
  5638.                                 }
  5639.                             }
  5640.                         });
  5641.                     }
  5642.                
  5643.                
  5644.                
  5645.                
  5646.                     if (showConfigs) {
  5647.                         // Extra links on village info to troop overview
  5648.                         (function() {
  5649.                             var properties = {},
  5650.                                 i;
  5651.                
  5652.                             function addSetting(properties, index, offDef, propName, label, editor, tooltip) {
  5653.                                 properties['infoPage_extra_link'+index+'_'+offDef+propName] = {
  5654.                                     label: label,
  5655.                                     tooltip: tooltip,
  5656.                                     propUI: {
  5657.                                         getter: function() { return user_data.villageInfo4[index][offDef][propName]; },
  5658.                                         setter: function(value) { user_data.villageInfo4[index][offDef][propName] = value; },
  5659.                                         editor: editor
  5660.                                     }
  5661.                                 };
  5662.                             }
  5663.                
  5664.                             // hehe
  5665.                             function addSetting2(properties, index, offDef, propName, label, editor, tooltip) {
  5666.                                 properties['infoPage_extra_link'+index+'_'+offDef+propName] = {
  5667.                                     label: label,
  5668.                                     tooltip: tooltip,
  5669.                                     propUI: {
  5670.                                         getter: function() { return user_data.villageInfo4[index][offDef].filter[propName]; },
  5671.                                         setter: function(value) { user_data.villageInfo4[index][offDef].filter[propName] = value; },
  5672.                                         editor: editor
  5673.                                     }
  5674.                                 };
  5675.                             }
  5676.                
  5677.                             function add2Links(captured_index, offDef) {
  5678.                                 addSetting(properties, captured_index, offDef+"_link", "name", sangu_trans.villageInfo.linkName, "text");
  5679.                                 addSetting(properties, captured_index, offDef+"_link", "icon", sangu_trans.villageInfo.icon, "text");
  5680.                                 addSetting(properties, captured_index, offDef+"_link", "sort", sangu_trans.villageInfo.sort, "bool");
  5681.                                 addSetting(properties, captured_index, offDef+"_link", "changeSpeed", sangu_trans.villageInfo.changeSpeed, "unit");
  5682.                                 addSetting(properties, captured_index, offDef+"_link", "group", sangu_trans.villageInfo.group, "number", sangu_trans.villageInfo.groupTitle);
  5683.                
  5684.                                 properties['incomings_attackIdDesc'+captured_index+offDef+'_FilterTitle'] = {
  5685.                                     type: "subtitle",
  5686.                                     label: sangu_trans.villageInfo.filter.title
  5687.                                 };
  5688.                
  5689.                                 addSetting2(properties, captured_index, offDef+"_link", "active", sangu_trans.villageInfo.filter.title, "bool");
  5690.                                 addSetting2(properties, captured_index, offDef+"_link", "unit", sangu_trans.villageInfo.filter.unit, "unit");
  5691.                                 addSetting2(properties, captured_index, offDef+"_link", "amount", sangu_trans.villageInfo.filter.amount, "number|step=100");
  5692.                             }
  5693.                
  5694.                             for (i = 0; i < user_data.villageInfo4.length; i++) {
  5695.                                 (function() {
  5696.                                     var captured_index = i;
  5697.                
  5698.                                     properties['incomings_attackIdDesc'+captured_index+'_title'] = {
  5699.                                         type: "subtitle",
  5700.                                         label: sangu_trans.villageInfo.title2 + " " + (captured_index + 1)
  5701.                                     };
  5702.                
  5703.                                     properties['infoPage_extra_link'+captured_index+'_activate'] = {
  5704.                                         label: sangu_trans.global.resources.activate,
  5705.                                         propUI: {
  5706.                                             getter: function() { return user_data.villageInfo4[captured_index].active; },
  5707.                                             setter: function(value) { user_data.villageInfo4[captured_index].active = value; },
  5708.                                             editor: "bool"
  5709.                                         }
  5710.                                     };
  5711.                
  5712.                                     properties['incomings_attackIdDesc'+captured_index+"off"+'_title'] = {
  5713.                                         type: "subtitle",
  5714.                                         label: sangu_trans.villageInfo.off_title
  5715.                                     };
  5716.                                     add2Links(captured_index, "off");
  5717.                
  5718.                                     properties['incomings_attackIdDesc'+captured_index+"def"+'_title'] = {
  5719.                                         type: "subtitle",
  5720.                                         label: sangu_trans.villageInfo.def_title
  5721.                                     };
  5722.                                     add2Links(captured_index, "def");
  5723.                
  5724.                                 })();
  5725.                             }
  5726.                
  5727.                             user_data_configs.push({
  5728.                                 id: "villageInfoLinks",
  5729.                                 title: sangu_trans.villageInfo.title,
  5730.                                 save: sangu_saver,
  5731.                                 properties: properties
  5732.                             });
  5733.                         }());
  5734.                     }
  5735.                
  5736.                     return user_data_configs;
  5737.                 }());
  5738.                 (function() {
  5739.                     // contentPage is the place we will add the settings menu to
  5740.                     var contentPage = $("table:first td:last", content_value).attr("width", "99%"),
  5741.                         sanguTitle = "<h3 id='sanguConfigTitle'>" + trans.sp.sp.configuration.replace("{version}", sangu_version) + "</h3>";
  5742.                
  5743.                     function gimmeTheMoney() {
  5744.                         function createButton(paypalCode, euroAmount, tooltip) {
  5745.                             return '<div align="center">'
  5746.                                 + '<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">'
  5747.                                 + '<input type="hidden" name="cmd" value="_s-xclick">'
  5748.                                 + '<input type="hidden" name="hosted_button_id" value="' + paypalCode + '">'
  5749.                                 + '<input type="image" src="https://www.paypalobjects.com/nl_NL/BE/i/btn/btn_donate_SM.gif" border="0" name="submit" title="'+tooltip+'">'
  5750.                                 + '<img alt="" border="0" src="https://www.paypalobjects.com/en_US/i/scr/pixel.gif" width="1" height="1">'
  5751.                                 + '<br>' + trans.sp.sp.donate.buttonAmount.replace("{amount}", euroAmount)
  5752.                                 + '</form></div>';
  5753.                         }
  5754.                
  5755.                         var html = "<h3>Contributors</h3>";
  5756.                         html += "Hebben aan het Sangu Package meegewerkt: <br><b>";
  5757.         html += "De Goede Fee, Tjeerdo, cgrain, Hardcode93";
  5758.                         html += "</b>";
  5759.                
  5760.                         html += "<h3>"+trans.sp.sp.donate.title+"</h3>";
  5761.                         html += trans.sp.sp.donate.whyWouldI;
  5762.                         html += "<br>" + trans.sp.sp.donate.books
  5763.                             .replace("{abegin}", "<a target='_blank' href='http://www.amazon.com/wishlist/1RFQ21NSF4PAI/ref=cm_wl_prev_ret?_encoding=UTF8&reveal='>")
  5764.                             .replace("{aend}", "</a>");
  5765.                
  5766.                         html += "<br><br>"
  5767.                             + "<table width='100%'><tr>"
  5768.                             + "<td>" + createButton("FA9MAMFAYKANL", 5, trans.sp.sp.donate.beer) + "</td>"
  5769.                             + "<td>" + createButton("R9RX6XBCV6T4G", 10, trans.sp.sp.donate.food) + "</td>"
  5770.                             + "<td>" + createButton("ELG8Y2GLSXAVA", 20, trans.sp.sp.donate.yaye) + "</td>"
  5771.                             + "</tr>"
  5772.                             + "</table>";
  5773.                
  5774.                         return html;
  5775.                     }
  5776.                
  5777.                     // Reset sangu settings links
  5778.                     var resetForm = "<a href='#' id='resetSettings'>&raquo; " + trans.sp.sp.settings.reset + "</a>";
  5779.                     resetForm += "<br>";
  5780.                     resetForm += "<a href='#' id='resetAllSettings'>&raquo; " + trans.sp.sp.settings.resetAll + "</a>";
  5781.                
  5782.                     // skeleton injection
  5783.                     contentPage.html(sanguTitle + "<div id='sanguSettingsForm'>" + resetForm + gimmeTheMoney() + "</div>");
  5784.                
  5785.                     gimmeTheMoney();
  5786.                     $("#sanguSettingsForm").append("<br><br>");
  5787.                
  5788.                     $("#resetSettings").click(function() {
  5789.                         if (confirm(trans.sp.sp.settings.reset)) {
  5790.                             pers.set('sangusettings', '');
  5791.                             location.reload(false);
  5792.                         }
  5793.                         return false;
  5794.                     });
  5795.                
  5796.                     $("#resetAllSettings").click(function() {
  5797.                         if (confirm(trans.sp.sp.settings.resetAll)) {
  5798.                             pers.clear();
  5799.                             location.reload(false);
  5800.                         }
  5801.                         return false;
  5802.                     });
  5803.                
  5804.                     (function() {
  5805.                         var sanguSettingsForm,
  5806.                             configIterator,
  5807.                             settingFormTogglerHtml,
  5808.                             settingsFormsOpenFromPersistence,
  5809.                             adornButton = function(button) { $(button).css("background-color", user_data.colors.error); };
  5810.                
  5811.                         sanguSettingsForm = $("#sanguSettingsForm");
  5812.                
  5813.                         settingFormTogglerHtml = "<h3>" + trans.sp.sp.settings.configuration + "</h3>";
  5814.                         settingFormTogglerHtml +=
  5815.                             "<table class='vis' width='100%'><tr class='row_a'><th>"
  5816.                                 + trans.sp.sp.settings.configurationFormTogglerTooltip
  5817.                                 + "</th></tr><tr class='row_b'><td>";
  5818.                         for (configIterator = 0; configIterator < user_data_configs.length; configIterator++) {
  5819.                             settingFormTogglerHtml +=
  5820.                                 "<input type='button' value=\""
  5821.                                     + user_data_configs[configIterator].title
  5822.                                     + "\" id='"+user_data_configs[configIterator].id
  5823.                                     + "_button' class='editFormToggler' /> &nbsp;";
  5824.                         }
  5825.                         settingFormTogglerHtml += "</td></tr></table><br>";
  5826.                         sanguSettingsForm.append(settingFormTogglerHtml);
  5827.                         $(".editFormToggler", sanguSettingsForm).click(function() {
  5828.                             var openForms = "",
  5829.                                 linkedDiv = $("#" + this.id.replace("_button", ""));
  5830.                
  5831.                             if (linkedDiv.is(":visible")) {
  5832.                                 linkedDiv.hide();
  5833.                                 $(this).css("background-color", "");
  5834.                             } else {
  5835.                                 linkedDiv.fadeIn();
  5836.                                 adornButton(this);
  5837.                             }
  5838.                
  5839.                             $(".propertyEditFormContainer", sanguSettingsForm).each(function() {
  5840.                                 if ($(this).is(":visible")) {
  5841.                                     openForms += this.id + "|";
  5842.                                 }
  5843.                             });
  5844.                             pers.set("settingsFormsOpen", openForms);
  5845.                         });
  5846.                
  5847.                         // build the property handler editting form
  5848.                         for (configIterator = 0; configIterator < user_data_configs.length; configIterator++) {
  5849.                             buildConfigForm(sanguSettingsForm, user_data_configs[configIterator]);
  5850.                         }
  5851.                
  5852.                         settingsFormsOpenFromPersistence = pers.get("settingsFormsOpen");
  5853.                         $(".propertyEditFormContainer", sanguSettingsForm).each(function() {
  5854.                             if (settingsFormsOpenFromPersistence.indexOf(this.id+"|") > -1) {
  5855.                                 adornButton($("#" + this.id + "_button"));
  5856.                                 $(this).show();
  5857.                             }
  5858.                         });
  5859.                        
  5860.                         $('input[id*="mainTagger_otherButtonHitKey"]').on('keydown', function(e){
  5861.                             e.preventDefault();
  5862.                             e.stopPropagation();
  5863.                             var TagNumber = $(this).attr("id").match(/\d+/);
  5864.                             $(this).val('');
  5865.                             $(this).val(keyCodeMap[e.which].toUpperCase());
  5866.                             user_data.mainTagger2.otherDescs[TagNumber]["hitKey"] = $(this).val();
  5867.                             pers.set('sangusettings', JSON.stringify(user_data));
  5868.                             trackEvent("ScriptUsage", "SettingEdit", "1");
  5869.                         })
  5870.                        
  5871.                     })();
  5872.                
  5873.                     // notable contributors
  5874.                     (function() {
  5875.                         var notableHtml = "<u>" + trans.sp.sp.donate.notable + "</u>";
  5876.                         notableHtml += "<br><br><b>sakeb</b>: Nogmaals bedankt voor 'JavaScript: The Good Parts'! :)";
  5877.                         notableHtml += "<br><b>Daniel Ivanov</b>";
  5878.                         notableHtml += "<br><br>";
  5879.                
  5880.                         $("#sanguSettingsForm").append(notableHtml);
  5881.                     })();
  5882.                 })();
  5883.                 }
  5884.                 break;
  5885.  
  5886.             case "place":
  5887.                 /**
  5888.                  * {spVillage} The current village
  5889.                  */
  5890.                 var vilHome = getVillageFromCoords(game_data.village.coord);
  5891.                 if ($("#attack_name").size() > 0) {
  5892.                     // RALLYPOINT CONFIRM
  5893.             (function() {
  5894.                 //console.time("confirm");
  5895.                 try {
  5896.                     // reorder the page
  5897.                     if (user_data.proStyle) {
  5898.                         $("table:first", content_value).css("width", 500);
  5899.            
  5900.                         // Merge nightbonus & tribe claim statements (for OK button placement)
  5901.                         if (user_data.proStyle && (user_data.confirm.replaceTribeClaim || user_data.confirm.replaceNightBonus)) {
  5902.                             var header = $("h2:first", content_value);
  5903.                             var claim = $("h3.error:visible");
  5904.                             if (claim.size() != 0) {
  5905.                                 claim.each(function() {
  5906.                                     var $this = $(this);
  5907.                                     $this.hide();
  5908.                                     header.addClass("error").text(header.text() + " - " + $this.text());
  5909.                                 });
  5910.                             }
  5911.                         }
  5912.                     }
  5913.            
  5914.                     // extra attack button (always on the same place)
  5915.                     if (user_data.confirm.addExtraOkButton) {
  5916.                         $("h2:first", content_value).prepend("<input type=submit style='font-size: 10pt' id=focusPlaceHolder value='" + $("#troop_confirm_go").val() + "'><br>");
  5917.                         $("#focusPlaceHolder").click(function () {
  5918.                             $(this).attr("disabled", "disabled");
  5919.                             $("#troop_confirm_go").click();
  5920.                         });
  5921.                     }
  5922.            
  5923.                     // Catapult building images
  5924.                     if (user_data.confirm.addCatapultImages && $("#save_default_attack_building").length == 1) {
  5925.                         var dropdown = $("select[name='building']");
  5926.                         var buildingImages = "";
  5927.            
  5928.                         dropdown.find("option").each(function(index, value) {
  5929.                             buildingImages += "<img class='catapultSwitcher' title='" + trans.sp.command.catapultImageTitle + "' building='" + $(value).val() +"' src='https://www.tribalwars.vodka/graphic/buildings/" + $(value).val() + ".png'> ";
  5930.                         });
  5931.            
  5932.                         dropdown.parent().parent().before("<tr><td colspan=4>"+buildingImages+"</td></tr>");
  5933.                         $("img.catapultSwitcher").click(function() {
  5934.                             dropdown.val($(this).attr("building"));
  5935.                         });
  5936.                     }
  5937.            
  5938.                     var valueCells = $("table.vis:first td:odd", content_value);
  5939.                     var targetVillage = valueCells.first().text();
  5940.            
  5941.                     // remember last attack
  5942.                     // saved at the confirmation page so that we can't save
  5943.                     // invalid coordinates
  5944.                     var village = getVillageFromCoords(targetVillage);
  5945.                     if (village.isValid) {
  5946.                         pers.set("lastVil", village.coord);
  5947.                     }
  5948.            
  5949.                     var isAttack = $("input[name='attack']").val() == "true";
  5950.                     var isBarbarian = valueCells.eq(1).has("a").length === 0;
  5951.                     var player = (isBarbarian ? '' : valueCells.eq(1).text());
  5952.            
  5953.                     var unitsSent = {};
  5954.                     $.each(world_data.units, function (i, val) {
  5955.                         unitsSent[val] = parseInt($("input[name='" + val + "']", content_value).val(), 10);
  5956.                     });
  5957.            
  5958.                     // compare runtime with dodgetime
  5959.                     var unitsCalc = calcTroops(unitsSent);
  5960.                     var dodgeCookie = pers.getCookie("sanguDodge" + getQueryStringParam("village"));
  5961.                     if (dodgeCookie) {
  5962.                         dodgeCookie = dodgeCookie.split("~");
  5963.                         var durationCell = $("table.vis:first td:contains('" + trans.tw.command.walkingTimeTitle + "')", content_value).next();
  5964.                         var attackRunTime = getTimeFromTW(durationCell.text());
  5965.                         var dodgeTime = getTimeFromTW(dodgeCookie[1]);
  5966.            
  5967.                         var runtimeIsOk = attackRunTime.totalSecs >= dodgeTime.totalSecs;
  5968.                         var diffSecs = (attackRunTime.totalSecs - dodgeTime.totalSecs);
  5969.            
  5970.                         var dodgeCellText = "<table border=0 cellpadding=0 cellspacing=0 width='1%'><tr>";
  5971.                         dodgeCellText += "<td width='25%' align=center>" + durationCell.text() + "</td>";
  5972.                         dodgeCellText += "<td width='50%' align=center><b>" + (runtimeIsOk ? "&gt;&gt;&gt;" : "&lt;&lt;&lt;") + "</b></td>";
  5973.                         dodgeCellText += "<td width='25%' align=center nowrap>" + dodgeCookie[1] + "&nbsp;";
  5974.                         if (diffSecs > 0) {
  5975.                             dodgeCellText += trans.sp.command.dodgeMinuteReturn.replace("{minutes}", prettyDate(diffSecs * 2000, true)); // 2000 = Method expects milliseconds and distance is walked 2 times!
  5976.                         }
  5977.                         dodgeCellText += "</td>";
  5978.            
  5979.                         dodgeCellText += "</tr></table>";
  5980.                         durationCell.html(dodgeCellText);
  5981.            
  5982.                         if (!runtimeIsOk) {
  5983.                             durationCell.find("table").attr("title", trans.sp.command.dodgeNotFarEnough).css("background-color", user_data.colors.error).find("td").css("background-color", user_data.colors.error);
  5984.                         }
  5985.            
  5986.                         if (dodgeCookie[0] != "unit_" + unitsCalc.getSlowest()) {
  5987.                             $("h2:first", content_value).css("background-color", user_data.colors.error);
  5988.                         }
  5989.                     } else {
  5990.                         // If a dodgecookie is in use, nightbonus etc isn't relevant
  5991.                         unitsCalc.colorIfNotRightAttackType($("h2:first", content_value), isAttack);
  5992.                         var arrivalTime = getDateFromTodayTomorrowTW($.trim($("#date_arrival").text()));
  5993.                         if (user_data.proStyle && user_data.confirm.replaceNightBonus && isDateInNightBonus(arrivalTime)) {
  5994.                             $("#date_arrival").css("background-color", user_data.colors.error).css("font-weight", "bold");
  5995.                         }
  5996.                     }
  5997.            
  5998.                     if (user_data.attackAutoRename.active) {
  5999.                         // rename attack command
  6000.                         // cookie reading code in place.js
  6001.                         var villageCoord = $("input[name='x']", content_value).val() + '|' + $("input[name='y']", content_value).val();
  6002.                         var sent = buildAttackString(villageCoord, unitsSent, player, !isAttack);
  6003.                         document.title = game_data.village.coord + " -> " + sent;
  6004.            
  6005.                         var twInitialCommandName = (isAttack ? trans.tw.command.attackOn : trans.tw.command.supportFor) + targetVillage;
  6006.                         pers.setSession("attRen_" + game_data.village.id + '_' + twInitialCommandName, sent);
  6007.                     }
  6008.                 } catch (e) { handleException(e, "place-confirm"); }
  6009.                 //console.timeEnd("confirm");
  6010.             }());
  6011.                 }
  6012.                 // RALLYPOINT UNITS THERE
  6013.                 else if (current_page.mode === 'units' && location.href.indexOf('try=back') == -1) {
  6014.             (function() {
  6015.                 //console.time("units_back");
  6016.                 try {
  6017.                     var units_awayTable = $("#units_away").width("100%");
  6018.                     if (units_awayTable.size() != 0) {
  6019.                         // Troops in other villages
  6020.                         $("tr:first", units_awayTable).append(
  6021.                             "<th>" + trans.sp.place.distance + "</th>"
  6022.                                 + "<th>" + trans.sp.place.backOn + "</th>");
  6023.            
  6024.                         units_awayTable.find("tr:gt(0):even").each(function() {
  6025.                             var row = $(this),
  6026.                                 villageCoord = getVillageFromCoords(row.find("td:eq(1)").text());
  6027.            
  6028.                             if (!villageCoord.isValid) {
  6029.                                 row.append("<th>&nbsp;</th><th>&nbsp;</th>");
  6030.                             } else {
  6031.                                 var slowestUnit = null;
  6032.                                 var slowestUnitName = null;
  6033.                                 $.each(world_data.units, function (i, val) {
  6034.                                     var amount = $("td:eq(" + (i + 2) + "), th:eq(" + (i + 1) + ")", row).text();
  6035.                                     if (amount != '0') {
  6036.                                         if (slowestUnit == null || slowestUnit < world_data.unitsSpeed['unit_' + val]) {
  6037.                                             slowestUnitName = val;
  6038.                                             slowestUnit = world_data.unitsSpeed['unit_' + val];
  6039.                                         }
  6040.                                     }
  6041.                                 });
  6042.            
  6043.                                 var fields = getDistance(vilHome.x, villageCoord.x, vilHome.y, villageCoord.y, slowestUnitName);
  6044.                                 var extraColumns = "<td align=right>" + parseInt(fields.fields, 10) + "</td>";
  6045.                                 extraColumns += "<td>" + twDateFormat(fields.arrivalTime) + "</td>";
  6046.            
  6047.                                 row.append(extraColumns);
  6048.                             }
  6049.                         });
  6050.                     }
  6051.            
  6052.                     // Calculate distance and walkingtime to the villages
  6053.                     var unitsTable = $("form table:first");
  6054.                     $("tr:first", unitsTable).append('<th width="50"><span class="icon header population" title="' + trans.sp.all.population + '"></span></th><th>' + trans.sp.place.distance + '</th><th>' + trans.sp.place.backOn + '</th>');
  6055.                     unitsTable.find("tr:gt(0)").each(function () {
  6056.                         var pop = 0;
  6057.                         var row = $(this);
  6058.                         var slowestUnit = null;
  6059.                         var slowestUnitName = null;
  6060.            
  6061.                         $.each(world_data.units, function (i, val) {
  6062.                             var amount = parseInt($("td:eq(" + (i + 1) + "), th:eq(" + (i + 1) + ")", row).text(), 10);
  6063.                             if (amount !== 0) {
  6064.                                 pop += amount * world_data.unitsPositionSize[i];
  6065.            
  6066.                                 if (slowestUnit == null || slowestUnit < world_data.unitsSpeed['unit_' + val]) {
  6067.                                     slowestUnitName = val;
  6068.                                     slowestUnit = world_data.unitsSpeed['unit_' + val];
  6069.                                 }
  6070.                             }
  6071.                         });
  6072.            
  6073.                         var villageCoord = getVillageFromCoords(row.find("td:first").text());
  6074.                         var color = getStackColor(pop);
  6075.            
  6076.                         if (color !== "") {
  6077.                             $(this).append("<td align=right style='background-color: " + color + "'>" + formatNumber(pop) + "</td><td colspan=2>&nbsp;</td>");
  6078.                         } else {
  6079.                             var extraColumns = '<td align=right>' + formatNumber(pop) + '</td>';
  6080.                             if (!villageCoord.isValid) {
  6081.                                 extraColumns += "<td colspan=2 align=right>&nbsp;</td>";
  6082.                             } else {
  6083.                                 //q(vilHome.x + ':' + slowestUnitName);
  6084.                                 var dist = getDistance(vilHome.x, villageCoord.x, vilHome.y, villageCoord.y, slowestUnitName),
  6085.                                     fields = parseInt(dist.fields, 10);
  6086.            
  6087.                                 extraColumns += "<td align=right>" + fields + "</td><td>" + twDateFormat(dist.arrivalTime) + "</td>";
  6088.                                 $("td:first", this).append("&nbsp; <b>" + trans.sp.all.fieldsSuffix.replace("{0}", fields) + "</b>");
  6089.                                 $(this).addClass("toSort").attr("fields", fields);
  6090.                             }
  6091.                             $(this).append(extraColumns);
  6092.                         }
  6093.                     });
  6094.            
  6095.                     var checkboxAmount = $("input[type='checkbox']", unitsTable);
  6096.                     if (checkboxAmount.size() == 1) {
  6097.                         // village has just been taken over? auto check checkbox
  6098.                         checkboxAmount.attr("checked", true);
  6099.                     }
  6100.            
  6101.                     // Sort on distance
  6102.                     unitsTable.find("tr.toSort").sortElements(function (a, b) {
  6103.                         return parseInt($(a).attr("fields"), 10) < parseInt($(b).attr("fields"), 10) ? 1 : -1;
  6104.                     });
  6105.            
  6106.                     // are there incomings on the supporting villages?
  6107.                     if (server_settings.ajaxAllowed) {
  6108.                         unitsTable.find("tr.toSort").each(function() {
  6109.                             var row = $(this);
  6110.                             var villageUrl = $("a:first", this).attr("href");
  6111.                             ajax(villageUrl, function (villageDetails) {
  6112.                                 var villageOwner = $("table.vis:first tr:eq(4) a", villageDetails);
  6113.                                 if (villageOwner.text() != game_data.player.name) {
  6114.                                     $("td:first a", row).after(" [" + villageOwner.outerHTML() + "]");
  6115.                                 } else {
  6116.                                     var incomingTable = $("table th:contains('" + trans.tw.overview.incomingTroops + "')", villageDetails);
  6117.                                     if (incomingTable.size() > 0) {
  6118.                                         incomingTable = incomingTable.parent().parent();
  6119.                                         var incomingRows = $("tr:has(img[src*='attack'])", incomingTable);
  6120.                                         if (incomingRows.size() > 0) {
  6121.                                             var firstAttack = incomingRows.eq(0);
  6122.                                             var timeLeft = $("td:eq(2)", firstAttack).text();
  6123.                                             var arrivalDate = $("td:eq(1)", firstAttack).text();
  6124.            
  6125.                                             var lastAttack = incomingRows.last();
  6126.                                             var timeLeftLast = $("td:eq(2)", lastAttack).text();
  6127.                                             var arrivalDateLast = $("td:eq(1)", lastAttack).text();
  6128.            
  6129.                                             var amount = incomingRows.size();
  6130.            
  6131.                                             var attacksDesc;
  6132.                                             if (amount == 1) {
  6133.                                                 attacksDesc = trans.sp.place.onlyAttack
  6134.                                                 .replace("{timeLeftFirst}", timeLeft)
  6135.                                                 .replace("{arrivalDateFirst}", arrivalDate);
  6136.                                             } else {
  6137.                                                 attacksDesc = trans.sp.place.multipleAttack
  6138.                                                 .replace("{timeLeftFirst}", timeLeft)
  6139.                                                 .replace("{arrivalDateFirst}", arrivalDate)
  6140.                                                 .replace("{timeLeftLast}", timeLeftLast)
  6141.                                                 .replace("{arrivalDateLast}", arrivalDateLast)
  6142.                                                 .replace("{amount}", amount);
  6143.                                             }
  6144.            
  6145.                                             $("td:first input", row).after("&nbsp; <img src='graphic/command/attack.png' title='" + attacksDesc + "'>");
  6146.                                         }
  6147.                                     }
  6148.                                 }
  6149.                             });
  6150.                         });
  6151.                     }
  6152.                 } catch (e) { handleException(e, "place-units_back"); }
  6153.                 //console.timeEnd("units_back");
  6154.             }());
  6155.                 }
  6156.                 // RALLY POINT (DEFAULT)
  6157.                 else {
  6158.             (function() {
  6159.                 //console.time("place-place");
  6160.                 try {
  6161.                 // Auto rename attacks
  6162.                 if (user_data.attackAutoRename.active) {
  6163.                     // Less than ideal solution:
  6164.                     // Does not work properly when sending many attacks (ie snobtrain)
  6165.                     // In confirm.js the cookies are saved
  6166.            
  6167.                     var hasAttackRenamingCookieNeedle = pers.getWorldKey('attRen_' + game_data.village.id + '_');
  6168.                     for (var i = 0; i  <  sessionStorage.length; i++) {
  6169.                         var key = sessionStorage.key(i);
  6170.                         if (key.indexOf(hasAttackRenamingCookieNeedle) == 0) {
  6171.                             var twInitialCommandName = key.substr(hasAttackRenamingCookieNeedle.length);
  6172.                             //q("found:" + hasAttackRenamingCookieNeedle + " -> " + twInitialCommandName);
  6173.            
  6174.                             // ' is an invalid village name character so we don't need to escape
  6175.                             var commandLabel = $('.quickedit-label:contains("' + twInitialCommandName + '")');
  6176.                             if (commandLabel.length > 0 && server_settings.ajaxAllowed) {
  6177.                                 var sanguCommandName = sessionStorage.getItem(key);
  6178.            
  6179.                                 // Open the rename command form:
  6180.                                 commandLabel.parent().next().click();
  6181.            
  6182.                                 // Fill in new command name and click rename button
  6183.                                 var commandWrapper = commandLabel.parent().parent().parent(),
  6184.                                     commandForm = commandWrapper.find(".quickedit-edit");
  6185.            
  6186.                                 commandForm.find("input:first").val(sanguCommandName);
  6187.                                 commandForm.find("input:last").click();
  6188.            
  6189.                                 pers.removeSessionItem(key);
  6190.            
  6191.                                 if (commandLabel.closest("table").find("tr").length > 2) {
  6192.                                     commandLabel.closest("td").addClass("selected");
  6193.                                 }
  6194.                             }
  6195.                         }
  6196.                     }
  6197.                 }
  6198.            
  6199.                 $("#inputx,#inputy").focus(function() {
  6200.                     $(this).select();
  6201.                 });
  6202.            
  6203.                 // fill in coordinates? (links from troops overview page)
  6204.                 if (server_settings.autoFillCoordinatesAllowed && window.location.search.indexOf("&sanguX=") != -1) {
  6205.                     var match = window.location.search.match(/sanguX=(\d+)&sanguY=(\d+)/);
  6206.                     if (typeof match[1] !== "undefined") {
  6207.                         $("#inputx").val(match[1]);
  6208.                         $("#inputy").val(match[2]);
  6209.                     }
  6210.                 }
  6211.            
  6212.                 // Spice up rally point:
  6213.                 var speedCookie = spSpeedCookie();
  6214.            
  6215. (function() {
  6216.     try {
  6217.  
  6218.         // Show current selected speed + ability to change active speed
  6219.         $(".unit_link img", content_value).each(function() {
  6220.             $(this).attr("title", trans.sp.place.changeSpeedImageTooltips.replace("{originalTitle}", $(this).attr("title")));
  6221.         });
  6222.  
  6223.         $("#command-data-form a img").click(function () {
  6224.             var unit = this.src;
  6225.             unit = unit.substr(unit.lastIndexOf('/') + 1);
  6226.             unit = unit.substr(0, unit.lastIndexOf('.'));
  6227.             speedCookie = spSpeedCookie(unit);
  6228.             $("#command-data-form a img").css("border", "0px").filter("img[src*='" + unit + "']").css("border", "3px red solid");
  6229.  
  6230.             // lastvil
  6231.             var coord = getVillageFromCoords(pers.get("lastVil"));
  6232.             if (coord.isValid) {
  6233.                 var dist = getDistance(coord.x, vilHome.x, coord.y, vilHome.y, speedCookie);
  6234.                 $("#lastVilTime")[0].innerHTML = dist.html;
  6235.             }
  6236.  
  6237.             // targetVillage
  6238.             coord = getVillageFromCoords(spTargetVillageCookie());
  6239.             if (coord.isValid) {
  6240.                 dist = getDistance(coord.x, vilHome.x, coord.y, vilHome.y, speedCookie);
  6241.                 $("#targetVilTime")[0].innerHTML = dist.html;
  6242.             }
  6243.  
  6244.         }).filter("img[src*='" + speedCookie + "']").css("border", "3px red solid");
  6245.  
  6246.     } catch (e) { handleException(e, "place-activespeed"); }
  6247. }());
  6248. (function() {
  6249.     try {
  6250.         // sangupackage last village
  6251.         var cookie = pers.get("lastVil"),
  6252.             coord = getVillageFromCoords(cookie),
  6253.             dist;
  6254.  
  6255.         if (coord.isValid) {
  6256.             dist = getDistance(coord.x, vilHome.x, coord.y, vilHome.y, speedCookie);
  6257.             var htmlStr = printCoord(coord, "&raquo; " + trans.sp.all.last + ": " + coord.x + "|" + coord.y);
  6258.             htmlStr += " &nbsp; <span id=lastVilTime>" + dist.html + "</span>";
  6259.             $("#command-data-form").append(htmlStr);
  6260.         }
  6261.  
  6262.         // Add target village
  6263.         var targetVillage = getVillageFromCoords(spTargetVillageCookie());
  6264.         if (targetVillage.isValid) {
  6265.             dist = getDistance(targetVillage.x, vilHome.x, targetVillage.y, vilHome.y, speedCookie);
  6266.             $("#command-data-form").append("<br>" + printCoord(targetVillage, "&raquo; " + trans.sp.all.target + ": " + targetVillage.x + "|" + targetVillage.y) + " &nbsp;<span id=targetVilTime>" + dist.html + "</span>");
  6267.         }
  6268.     } catch (e) { handleException(e, "place-lastandtargetvillage"); }
  6269. }());
  6270. (function() {
  6271.     try {
  6272.         // Read troops available
  6273.         var units = [];
  6274.         units.total = 0;
  6275.         $("#command-data-form .unitsInput").each(function () {
  6276.             var amount = $(this).next().text().substr(1);
  6277.             units[this.name] = parseInt(amount.replace(")", ""), 10);
  6278.             units.total += units[this.name] * world_data.unitsSize['unit_'+this.name];
  6279.         });
  6280.  
  6281.         // Add extra links next to "All troops"
  6282.         function createRallyPointScript(linksContainer, unitLoop, name, min, checkFunction, tag) {
  6283.             send = {};
  6284.             $.each(unitLoop, function (i, v) {
  6285.                 if (units[v] >= min) {
  6286.                     send[v] = checkFunction(units[v], v, tag);
  6287.                 }
  6288.             });
  6289.             linksContainer.append("&nbsp; &nbsp;<a href='#' onclick='" + fillRallyPoint(send) + "; return false'>" + name + "</a>");
  6290.         }
  6291.  
  6292.         var villageType = calcTroops(units);
  6293.         var linksContainer = $('#selectAllUnits').parent().attr("colspan", 4);
  6294.  
  6295.         // add fake attack
  6296.         var minFake = 0;
  6297.         if (world_config.hasMinFakeLimit) {
  6298.             minFake = getBuildingPoints();
  6299.             minFake *= world_config.minFake;
  6300.             if (units.ram > 0) {
  6301.                 minFake -= world_data.unitsSize['unit_ram'];
  6302.             }
  6303.         }
  6304.  
  6305.         if (user_data.place.attackLinks.fakePlaceLink && units['total'] >= minFake) {
  6306.             createRallyPointScript(linksContainer, world_data.units, user_data.place.attackLinks.fakePlaceLinkName, 0, function (amount, v, tag) {
  6307.                 if ((v == 'ram' || v == 'catapult') && !tag.rammed && amount > 0) {
  6308.                     tag.rammed = true;
  6309.                     return 1;
  6310.                 }
  6311.  
  6312.                 if (v == 'snob' || tag.toSend <= 0 || amount == 0) {
  6313.                     return 0;
  6314.                 }
  6315.  
  6316.                 if (user_data.place.attackLinks.fakePlaceExcludeTroops.indexOf(v) > -1) {
  6317.                     return 0;
  6318.                 }
  6319.  
  6320.                 var farmSize = world_data.unitsSize['unit_' + v];
  6321.                 if (amount * farmSize > tag.toSend) {
  6322.                     amount = Math.ceil(tag.toSend / farmSize);
  6323.                 }
  6324.                 tag.toSend -= amount * farmSize;
  6325.                 if (v == 'sword' && amount > 0) {
  6326.                     tag.toSend++;
  6327.                     amount--;
  6328.                 }
  6329.  
  6330.                 return amount;
  6331.             }, { toSend: minFake, rammed: false });
  6332.         }
  6333.  
  6334.         if (units['total'] > 0)
  6335.             $.each(user_data.place.customPlaceLinks, function (i, v) {
  6336.                 if (v.active && villageType.isMatch(v.type)) {
  6337.                     // villageType: off, def, all
  6338.                     if (v.required == undefined || units[v.required[0]] >= v.required[1]) {
  6339.                         // requires certain amount of troops
  6340.                         if (v.totalPop == undefined) {
  6341.                             // work with absolute numbers
  6342.                             createRallyPointScript(linksContainer, world_data.units, v.name, 0, function (amount, unitVal, tag) {
  6343.                                 //q(v + ' - SEND:' + tag[v] + '; amount=' + amount + ';');
  6344.                                 var send = tag[unitVal];
  6345.                                 if (send != undefined && amount > 0) {
  6346.                                     //q("send: " + send + " // amount: " + amount + " // unitVal: " + unitVal);
  6347.                                     if (send < 0) {
  6348.                                         send = amount + send;
  6349.                                         if (send < 0) {
  6350.                                             send = 1;
  6351.                                         }
  6352.                                     }
  6353.                                     if ((amount - send) * world_data.unitsSize['unit_' + unitVal] < tag.sendAlong) {
  6354.                                         send = amount;
  6355.                                     }
  6356.                                     if (send > 0 && !tag.ignoreNobles) {
  6357.                                         $.each(user_data.place.attackLinks.nobleSupport, function (i, val) {
  6358.                                             if (unitVal == val.unit && villageType.isMatch(val.villageType)) {
  6359.                                                 send -= Math.ceil(units.snob * val.amount);
  6360.                                             }
  6361.                                         });
  6362.                                     }
  6363.                                     //if (unitVal == 'light') q(send);
  6364.  
  6365.                                     if (send > amount) {
  6366.                                         return amount;
  6367.                                     }
  6368.                                     if (send > 0) {
  6369.                                         return send;
  6370.                                     }
  6371.                                 }
  6372.                                 return 0;
  6373.                             }, v);
  6374.  
  6375.                         } else { // do automatic calculation which division of troops to select
  6376.                             ////{ active: true, type: 'def', name: 'HelftZc', totalPop: 10000, divideOver: ['spear', 'heavy'] },
  6377.                             // TODO this doesn't yet work, does it?
  6378.                             // Probably not active...
  6379.                             var totalPop = 0;
  6380.                             $.each(v.divideOver, function (i, val) { totalPop += units[val] * world_data.unitsSize['unit_' + val]; });
  6381.  
  6382.                             createRallyPointScript(linksContainer, world_data.units, v.name, 0, function (amount, unitVal, tag) {
  6383.                                 if ($.inArray(unitVal, tag.divideOver) == -1) {
  6384.                                     return 0;
  6385.                                 }
  6386.                                 if (totalPop < tag.totalPop) {
  6387.                                     return amount;
  6388.                                 }
  6389.  
  6390.                                 var currentUnitPercentage = (amount * world_data.unitsSize['unit_' + unitVal]) / totalPop;
  6391.                                 return Math.floor(amount * currentUnitPercentage);
  6392.                             }, v);
  6393.                         }
  6394.                     }
  6395.                 }
  6396.             });
  6397.  
  6398.         if (units.spy >= user_data.place.attackLinks.scoutVillage && user_data.place.attackLinks.scoutPlaceLinks != null && user_data.place.attackLinks.scoutPlaceLinks.length > 0) {
  6399.             $.each(user_data.place.attackLinks.scoutPlaceLinks, function (i, v) {
  6400.                 if (units.spy >= v) {
  6401.                     createRallyPointScript(linksContainer, ["spy"], user_data.place.attackLinks.scoutPlaceLinksName.replace("{amount}", v), 0, function (amount, v, tag) {
  6402.                         return tag;
  6403.                     }, v);
  6404.                 }
  6405.             });
  6406.         }
  6407.  
  6408.         if (units.snob > 0 && user_data.place.attackLinks.noblePlaceLink) {
  6409.             if (user_data.place.attackLinks.noblePlaceLinkFirstName) {
  6410.                 createRallyPointScript(linksContainer, world_data.units, user_data.place.attackLinks.noblePlaceLinkFirstName, 0, function (amount, v, tag) {
  6411.                     if (v == 'snob') {
  6412.                         return 1;
  6413.                     }
  6414.                     if (tag > 0) {
  6415.                         var returned = null;
  6416.                         $.each(user_data.place.attackLinks.nobleSupport, function (i, val) {
  6417.                             if (v == val.unit && villageType.isMatch(val.villageType)) {
  6418.                                 returned = amount - Math.ceil((tag - 1) * val.amount);
  6419.                             }
  6420.                         });
  6421.                         if (returned != null) {
  6422.                             return returned;
  6423.                         }
  6424.                     }
  6425.                     return amount;
  6426.                 }, units.snob);
  6427.             }
  6428.  
  6429.             if (user_data.place.attackLinks.noblePlaceLinkSupportName && (units.snob > 1 || user_data.place.attackLinks.noblePlaceLinksForceShow)) {
  6430.                 createRallyPointScript(linksContainer, world_data.units, user_data.place.attackLinks.noblePlaceLinkSupportName, 0, function (amount, v, tag) {
  6431.                     if (v == 'snob') {
  6432.                         return 1;
  6433.                     }
  6434.                     var returned = 0;
  6435.                     $.each(user_data.place.attackLinks.nobleSupport, function (i, val) {
  6436.                         if (v == val.unit && villageType.isMatch(val.villageType)) {
  6437.                             returned = Math.ceil(1 * val.amount);
  6438.                         }
  6439.                     });
  6440.                     return returned;
  6441.                 });
  6442.             }
  6443.  
  6444.             if (units.snob > 0 && user_data.place.attackLinks.noblePlaceLinkDivideName) {
  6445.                 createRallyPointScript(linksContainer, world_data.units, user_data.place.attackLinks.noblePlaceLinkDivideName, 0, function (amount, v, tag) {
  6446.                     if (v == 'snob') {
  6447.                         return 1;
  6448.                     }
  6449.                     if (v == 'catapult') {
  6450.                         return 0;
  6451.                     }
  6452.                     if (v == 'ram' && !user_data.place.attackLinks.noblePlaceLinkDivideAddRam) {
  6453.                         return 0;
  6454.                     }
  6455.                     return Math.floor(amount / units.snob);
  6456.                 });
  6457.             }
  6458.         }
  6459.     } catch (e) { handleException(e, "place-extratrooplinks"); }
  6460. }());
  6461.            
  6462.                 } catch (e) { handleException(e, "place-place"); }
  6463.                 //console.timeEnd("place-place");
  6464.             }());
  6465.                 }
  6466.                 break;
  6467.  
  6468.             case "overview_villages":
  6469.                 break;
  6470.         }
  6471.  
  6472.         // USERPROFIEL++ // INFO_ ALLY/PLAYER
  6473.         if ((current_page.screen === 'info_ally' || current_page.screen === 'info_player' || current_page.screen === 'info_village')
  6474.             || (current_page.screen === "ally" && current_page.mode === "profile")) {
  6475.  
  6476.             (function() {
  6477.                 try {
  6478.                     var tables = $('table.vis', content_value),
  6479.                         villageInfoTable = tables.first(),
  6480.                         commandsTable = villageInfoTable.next('table'),
  6481.                         profile = user_data.profile,
  6482.                         i;
  6483.            
  6484.                     if (game_data.features.Premium.active && location.href.indexOf('screen=info_village') > -1) {
  6485.                         // extra links on the village overview page
  6486.                         var createFilterLink = function(baseLink, settings) {
  6487.                             var link = baseLink + "&group=" + settings.group + "&sort=" + settings.sort + "&changeSpeed=" + settings.changeSpeed;
  6488.            
  6489.                             if (settings.filter.active) {
  6490.                                 link += "&unit=" + settings.filter.unit + "&amount=" + settings.filter.amount;
  6491.                             }
  6492.            
  6493.                             var fancyIcon = settings.icon ? "<img src='" + settings.icon + "'>" : "";
  6494.                             return "<a href='" + link + "'>" + fancyIcon + settings.name + "</a>";
  6495.                         };
  6496.            
  6497.                         for (i = 0; i < user_data.villageInfo4.length; i++) {
  6498.                             var currentPairInfo = user_data.villageInfo4[i];
  6499.                             if (currentPairInfo.active) {
  6500.                                 var id = villageInfoTable.find("td:eq(2)").text(),
  6501.                                     link = getUrlString(
  6502.                                         "&screen=overview_villages&type=own_home&mode=units&page=-1&targetvillage="
  6503.                                             + id.substr(id.lastIndexOf("=") + 1));
  6504.            
  6505.                                 commandsTable.find("tbody:first").append(
  6506.                                     "<tr><td>" + createFilterLink(link, currentPairInfo.off_link) + "</td>"
  6507.                                         + "<td>" + createFilterLink(link, currentPairInfo.def_link) + "</td><tr>");
  6508.                             }
  6509.                         }
  6510.                     }
  6511.            
  6512.                     if (user_data.profile.show && (location.href.indexOf('screen=info_village') == -1 || user_data.showPlayerProfileOnVillage)) {
  6513.                         var screen;
  6514.                         var mapProfile = user_data.profile.mapLink;
  6515.                         var isVillage = false;
  6516.                         if (current_page.screen !== 'info_ally' && current_page.screen !== "ally") {
  6517.                             // player and village info page
  6518.                             // Extra links and info in table at the left top
  6519.                             screen = "player";
  6520.                             if (user_data.proStyle) {
  6521.                                 $("td:first", content_value).closest('table').hasClass('modemenu')
  6522.                                     ? $(content_value).find('table:has("#villages_list") td:first').css("width", "40%").next().css("width", "60%")
  6523.                                     : $("td:first", content_value).css("width", "40%").next().css("width", "60%");
  6524.                             }
  6525.            
  6526.                             if (current_page.screen === 'info_player') {
  6527.                                 // player info page
  6528.                                 id = commandsTable.find("a[href*='screen=mail']").attr("href");
  6529.            
  6530.                                 if (id == undefined) {
  6531.                                     id = game_data.player.id;
  6532.                                 } else {
  6533.                                     id = id.substr(id.indexOf("&player=") + 8);
  6534.                                     //alert(id);
  6535.                                     if (id.indexOf("&") > -1) {
  6536.                                         id = id.substr(0, id.indexOf("&"));
  6537.                                     }
  6538.                                 }
  6539.                             } else {
  6540.                                 // village info page
  6541.                                 isVillage = true;
  6542.                                 commandsTable = $("table.vis:first", content_value);
  6543.                                 id = commandsTable.find("tr:eq(3) a");
  6544.                                 //assert(id.size() == 1, "player id not found on info_village page");
  6545.                                 if (id.size() > 0) {
  6546.                                     id = id.attr("href").match(/id=(\d+)/)[1];
  6547.                                     assert(!!id, "player id href is not set");
  6548.                                 } else {
  6549.                                     id = 0;
  6550.                                 }
  6551.                             }
  6552.            
  6553.                             // Direct link to TW Stats map
  6554.                             if (id > 0 && profile.mapLink.show) {
  6555.                                 var link = "https://" + game_data.market + ".twstats.com/" + game_data.world + "/index.php?page=map";
  6556.                                 var tribeId = commandsTable.prev('table').find("td:eq(8) a");
  6557.                                 //assert(tribeId.size() == 1, "tribe id not found"); // Not everyone is in a tribe
  6558.                                 if (tribeId.size() == 1) {
  6559.                                     tribeId = tribeId.attr("href").match(/id=(\d+)/)[1];
  6560.                                     assert(!isNaN(tribeId), "tribe id is not a number");
  6561.                                 } else tribeId = 0;
  6562.            
  6563.                                 if (mapProfile.tribeColor != null) {
  6564.                                     link += "&tribe_0_id=" + tribeId + "&tribe_0_colour=" + mapProfile.tribeColor.substr(1);
  6565.                                 }
  6566.                                 if (mapProfile.yourTribeColor != null && game_data.player.ally_id != tribeId && game_data.player.ally_id > 0) {
  6567.                                     link += "&tribe_1_id=" + game_data.player.ally_id + "&tribe_1_colour=" + mapProfile.yourTribeColor.substr(1);
  6568.                                 }
  6569.                                 link += "&player_0_id=" + id + "&player_0_colour=" + mapProfile.playerColor.substr(1);
  6570.                                 link += "&grid=" + (mapProfile.grid ? 1 : 0) + "&fill=" + mapProfile.fill.substr(1) + "&zoom=" + mapProfile.zoom + "&centrex=" + mapProfile.centreX + "&centrey=" + mapProfile.centreY;
  6571.                                 if (mapProfile.markedOnly) {
  6572.                                     link += "&nocache=1";
  6573.                                 }
  6574.                                 if (mapProfile.bigMarkers) {
  6575.                                     link += "&bm=1";
  6576.                                 }
  6577.                                 if (mapProfile.gridContinentNumbers) {
  6578.                                     link += "&kn=1";
  6579.                                 }
  6580.                                 if (mapProfile.ownColor != null && game_data.player.id != id) {
  6581.                                     link += "&player_1_id=" + game_data.player.id + "&player_1_colour=" + mapProfile.ownColor.substr(1);
  6582.                                 }
  6583.                                 commandsTable.find("tr:last").after("<tr><td colspan=2><a href='" + link + "' target='_blank'>&raquo; " + trans.sp.profile.twStatsMap + "</a> " + trans.sp.profile.externalPage + "</td></tr>");
  6584.                             }
  6585.            
  6586.                             if (!isVillage) {
  6587.                                 // Amount of villages
  6588.                                 if (user_data.proStyle) {
  6589.                                     // always show villagename on one line
  6590.                                     var colWidth = $("table:eq(2) th", content_value);
  6591.                                     colWidth.first().css("width", "98%");
  6592.                                     colWidth.eq(1).css("width", "1%");
  6593.                                     colWidth.eq(2).css("width", "1%");
  6594.                                 }
  6595.            
  6596.                                 var amountOfVillages = tables.eq(2).find("th:first").text();
  6597.                                 amountOfVillages = amountOfVillages.substr(amountOfVillages.indexOf("(") + 1);
  6598.                                 amountOfVillages = amountOfVillages.substr(0, amountOfVillages.length - 1);
  6599.                                 commandsTable.prev('table').find("tr:eq(2)").after("<tr><td>" + trans.sp.profile.villages + "</td><td>" + formatNumber(amountOfVillages) + "</td></tr>");
  6600.                             }
  6601.                         } else {
  6602.                             screen = "tribe";
  6603.                             if (current_page.screen === 'info_ally') {
  6604.                                 commandsTable = tables.eq(0);
  6605.                             }
  6606.                             id = commandsTable.find("a");
  6607.                             id = id.last().attr("href").match(/&id=(\d+)/)[1];
  6608.            
  6609.                             var link = "https://" + game_data.market + ".twstats.com/" + game_data.world + "/index.php?page=map";
  6610.                             link += "&tribe_0_id=" + id + "&tribe_0_colour=" + mapProfile.tribeColor.substr(1);
  6611.                             link += "&centrex=" + mapProfile.centreX + "&centrey=" + mapProfile.centreY;
  6612.                             if (mapProfile.yourTribeColor != null && game_data.player.ally_id != id) {
  6613.                                 link += "&tribe_1_id=" + game_data.player.ally_id + "&tribe_1_colour=" + mapProfile.yourTribeColor.substr(1);
  6614.                             }
  6615.                             link += "&grid=" + (mapProfile.grid ? 1 : 0) + "&fill=" + mapProfile.fill.substr(1) + "&zoom=" + mapProfile.zoom
  6616.                             if (mapProfile.markedOnly) {
  6617.                                 link += "&nocache=1";
  6618.                             }
  6619.                             if (mapProfile.bigMarkers) {
  6620.                                 link += "&bm=1";
  6621.                             }
  6622.                             if (mapProfile.gridContinentNumbers) {
  6623.                                 link += "&kn=1";
  6624.                             }
  6625.                             if (mapProfile.ownColor != null) {
  6626.                                 link += "&player_0_id=" + game_data.player.id + "&player_0_colour=" + mapProfile.ownColor.substr(1);
  6627.                             }
  6628.                             commandsTable.find("tr:last").before("<tr><td colspan=2><a href='" + link + "' target='_blank'>&raquo; " + trans.sp.profile.twStatsMap + "</a> " + trans.sp.profile.externalPage + "</td></tr>");
  6629.                         }
  6630.            
  6631.                         // Build graphs
  6632.                         if (id > 0) {
  6633.                             var html = "";
  6634.            
  6635.                             // TWMap graphs
  6636.                             var twMapGraphs;
  6637.                             if (screen == "tribe") {
  6638.                                 twMapGraphs = [["tribe", trans.sp.profile.graphTWMap], ["p_tribe", trans.sp.profile.graphPoints], ["oda_tribe", trans.sp.profile.graphODA], ["odd_tribe", trans.sp.profile.graphODD]];
  6639.                             } else {
  6640.                                 twMapGraphs = [["player", trans.sp.profile.graphTWMap], ["p_player", trans.sp.profile.graphPoints], ["oda_player", trans.sp.profile.graphODA], ["odd_player", trans.sp.profile.graphODD], ["ods_player", trans.sp.profile.graphODS]];
  6641.                             }
  6642.                             for (var i = 0; i < twMapGraphs.length; i++) {
  6643.                                 var graphDetails = screen == "tribe" ? profile.twMapTribeGraph[twMapGraphs[i][0]] : profile.twMapPlayerGraph[twMapGraphs[i][0]];
  6644.                                 if (graphDetails[0]) {
  6645.                                     html += createSpoiler(twMapGraphs[i][1], '<img src="http://' + game_data.world + '.tribalwarsmap.com/' + game_data.market + '/graph/' + twMapGraphs[i][0] + '/' + id + '" title="' + trans.sp.profile.graphTWMap + '">', graphDetails[1]);
  6646.                                 }
  6647.                             }
  6648.            
  6649.                             // TWStats graphs
  6650.                             var graphs = [["points", trans.sp.profile.graphPoints], ["villages", trans.sp.profile.graphVillages], ["od", trans.sp.profile.graphOD], ["oda", trans.sp.profile.graphODA], ["odd", trans.sp.profile.graphODD], ["rank", trans.sp.profile.graphRank]];
  6651.                             if (screen == "tribe") {
  6652.                                 graphs.push(["members", trans.sp.profile.graphMembers]);
  6653.                             }
  6654.                             var toShow = screen == "tribe" ? profile.tribeGraph : profile.playerGraph;
  6655.                             for (var i = 0; i < graphs.length; i++) {
  6656.                                 if (toShow[i][1]) {
  6657.                                     var graphType = toShow[i][1] == 'big' ? 'ss' : '';
  6658.                                     html += createSpoiler(graphs[i][1], '<img src="https://' + game_data.market + '.twstats.com/image.php?type=' + screen + graphType + 'graph&id=' + id + '&s=' + game_data.world + '&graph=' + graphs[i][0] + '">', toShow[i][2] != undefined);
  6659.                                 }
  6660.                             }
  6661.            
  6662.                             // Show graphs
  6663.                             if (html.length > 0) {
  6664.                                 var pictureTable;
  6665.                                 if (screen == 'player' || (isVillage && user_data.showPlayerProfileOnVillage)) {
  6666.                                     pictureTable = $(content_value).find('table:has("th:first:contains(Profiel)")'); // TODO: untranslated resource Profiel
  6667.                                     if (isVillage || pictureTable.html() == null) {
  6668.                                         // With no info nor personal text
  6669.                                         pictureTable = $("<table class='vis' width='100%'><tr><th colspan='2'>" + trans.tw.profile.title + "</th></tr></table>");
  6670.                                         $("td:first", content_value).closest('table').hasClass('modemenu') ? $(content_value).find('table:has("#villages_list") td:first').next().prepend(pictureTable) : $("td:first", content_value).next().prepend(pictureTable);
  6671.                                     } else if (pictureTable.find("th").text() != trans.tw.profile.title) {
  6672.                                         // TODO: There is a ; after the IF, is that the intention???
  6673.                                         if (pictureTable.find("th:first").text() == trans.tw.profile.awardsWon);
  6674.                                         pictureTable = pictureTable.parent();
  6675.            
  6676.                                         // If there is only the node "Personal info"
  6677.                                         var temp = $("<table class='vis' width='100%'><tr><th colspan='2'>" + trans.tw.profile.title + "</th></tr></table>");
  6678.                                         pictureTable.prepend(temp);
  6679.                                         pictureTable = temp;
  6680.                                     }
  6681.            
  6682.                                     if (pictureTable.find("td[colspan=2]").size() > 0) {
  6683.                                         pictureTable.find("tr:last").before("<tr><td colspan=2>" + html + "</td></tr>");
  6684.                                     } else {
  6685.                                         pictureTable.find("tr:last").after("<tr><td colspan=2>" + html + "</td></tr>");
  6686.                                     }
  6687.                                 } else {
  6688.                                     commandsTable.after("<table class=vis width='100%'><tr><th>" + trans.tw.profile.title + "</th></tr><tr><td>" + html + "</td></tr></table>");
  6689.                                 }
  6690.                             }
  6691.                         }
  6692.            
  6693.                         // Conquers (intern)
  6694.                         if (id > 0 && profile.popup.show) {
  6695.                             var twLink = 'https://' + game_data.market + '.twstats.com/' + game_data.world + '/index.php?page=' + screen + '&mode=conquers&id=' + id + '&pn=1&type=1&enemy=-1&enemyt=-1&min=&max=';
  6696.                             var conquers = "<tr><td colspan=2><a href=\"\" id='conquers'>&raquo; " + trans.sp.profile.conquers + "</a> " + trans.sp.profile.internalPage + "</td></tr>";
  6697.                             if (screen == 'tribe') {
  6698.                                 commandsTable.find("tr:last").before(conquers);
  6699.                             } else {
  6700.                                 commandsTable.find("tr:last").after(conquers);
  6701.                             }
  6702.                             var popupWidth = profile.popup.width;
  6703.                             var popupHeight = profile.popup.height;
  6704.                             commandsTable.after('<div class="messagepop pop" id="popup" style="display: none"><iframe src=' + twLink + ' width=' + popupWidth + ' height=' + popupHeight + '></div>');
  6705.                             $("#popup").css({ "left": profile.popup.left, "top": profile.popup.top, "background-color": "#FFFFFF", "border": "1px solid #999999", "position": "absolute", "width": popupWidth, "height": popupHeight, "z-index": 50, "padding": "25px 25px 20px" });
  6706.            
  6707.                             $(function () {
  6708.                                 $("#conquers").on('click', function (event) {
  6709.                                     if ($(this).hasClass('selected')) {
  6710.                                         $("#conquers").removeClass("selected");
  6711.                                     } else {
  6712.                                         $(this).addClass("selected");
  6713.                                     }
  6714.                                     $("#popup").toggle();
  6715.                                     return false;
  6716.                                 });
  6717.            
  6718.                                 $("#popup").on('click', function () {
  6719.                                     $("#popup").hide();
  6720.                                     $("#conquers").removeClass("selected");
  6721.                                     return false;
  6722.                                 });
  6723.                             });
  6724.                         }
  6725.                     }
  6726.            
  6727.                     if (current_page.screen === 'info_village' && user_data.proStyle && profile.moveClaim) {
  6728.                         // move claim to a position that does not interfere with more important links (2-click behavior)
  6729.                         if ($("td:eq(8)", commandsTable).text() == trans.tw.profile.claimedBy) {
  6730.                             commandsTable.append($("tr:eq(5),tr:eq(6)", commandsTable));
  6731.                         }
  6732.                     }
  6733.                 } catch (e) { handleException(e, "info_villageplayertribe"); }
  6734.             }());
  6735.         }
  6736.         if (current_page.screen === 'info_village') {
  6737.             //Written by hardcode
  6738.             (function() {
  6739.                 var show = true;
  6740.                 var head = $(".vis:contains('Aankomend')", content_value).find("tr:first");
  6741.                 var text = $("th:contains('Aankomst:')", content_value).html('Aankomend: <img src="https://www.tribalwars.vodka/graphic/minus.png" style=" float: right;">');
  6742.            
  6743.                 head.on("click", function () {
  6744.                     if(show === true) {
  6745.                         $(".no_ignored_command").hide();
  6746.                         text.html('Verborgen: <img src="https://www.tribalwars.vodka/graphic/plus.png" style=" float: right;">');
  6747.                         show = false;
  6748.                     }else{
  6749.                         $(".no_ignored_command").show();
  6750.                         text.html('Aankomend: <img src="https://www.tribalwars.vodka/graphic/minus.png" style=" float: right;">');
  6751.                         show = true;
  6752.                     }
  6753.                 });
  6754.             }());
  6755.         }
  6756.  
  6757.         // ALL OVERVIEW PAGES
  6758.         if (current_page.screen === 'overview_villages') {
  6759.             var overviewTable;
  6760.             //tableHandler.overviewTable
  6761.            
  6762.             var tableHandler;
  6763.             (function (tableHandler) {
  6764.                 function init(id, options) {
  6765.                     tableHandler.overviewTableName = id;
  6766.                     tableHandler.overviewTable = $("#" + id);
  6767.                     tableHandler.settings = {
  6768.                         hasBottomTotalRow: false
  6769.                        
  6770.                     };
  6771.                     tableHandler.settings = $.extend({}, tableHandler.settings, options || {});
  6772.                    
  6773.                     // do stuff on page load
  6774.                     ajaxLoadNextPageSetup();
  6775.                 }
  6776.                 tableHandler.init = init;
  6777.                
  6778.                 function getReplacedVillageRows(page) {
  6779.                     var overviewTable = typeof page === 'undefined' ? tableHandler.overviewTable : $("#"+tableHandler.overviewTableName, page);
  6780.                     if (typeof tableHandler.settings.rowReplacer === "function") {
  6781.                         var newTable = "";
  6782.                         var villageRows = selectVillageRows(overviewTable);
  6783.                         villageRows.each(function () {
  6784.                             var row = $(this);
  6785.                             newTable += tableHandler.settings.rowReplacer(row);
  6786.                         });
  6787.                         return newTable;
  6788.                     } else {
  6789.                         return selectVillageRows(overviewTable);
  6790.                     }
  6791.                 }
  6792.                 tableHandler.getReplacedVillageRows = getReplacedVillageRows;
  6793.                
  6794.                 function selectVillageRows(page) {
  6795.                     //q(tableHandler.overviewTableName);
  6796.                     //q("grrrr:"+page.find("tr").not(":first").length);
  6797.                     var villageRows = page.find("tr").not(":first");
  6798.                     if (tableHandler.settings.hasBottomTotalRow) {
  6799.                         villageRows = villageRows.not(":last");
  6800.                     }
  6801.                     //q(villageRows);
  6802.                     return villageRows;
  6803.                 }
  6804.                
  6805.                 function ajaxLoadNextPageSetup() {
  6806.                     if (server_settings.ajaxAllowed) {
  6807.                         var nextPageLink = $("#paged_view_content a.paged-nav-item").first();
  6808.                         if (nextPageLink.length !== 0) {
  6809.                             // find all pages we can still add to the current table
  6810.                             var currentPageLabel = nextPageLink.parent().find("strong");
  6811.                             var nextPageLinks = [];
  6812.                             currentPageLabel = currentPageLabel.next();
  6813.                             while (currentPageLabel.text().match(/\d/)) {
  6814.                                 nextPageLinks.push(currentPageLabel);
  6815.                                 currentPageLabel = currentPageLabel.next();
  6816.                             }
  6817.                            
  6818.                             if (nextPageLinks.length > 0) {
  6819.                                 nextPageLink.parent().append("&nbsp; <a href=# id=loadNextPage>"+trans.sp.overviews.loadNextPage+"</a>");
  6820.                                
  6821.                                 $("#loadNextPage").click(function() {
  6822.                                     // Get next page or remove link
  6823.                                     var nextPageUrl = nextPageLinks[0];
  6824.                                     if (nextPageLinks.length == 1) {
  6825.                                         $(this).replaceWith("<strong>"+trans.sp.overviews.loadNextPage+"</strong>");
  6826.                                     }
  6827.                                     nextPageLinks.shift();
  6828.                                    
  6829.                                     // Fetch and insert next page
  6830.                                     ajax(nextPageUrl.attr("href"), function (page) {
  6831.                                         var nextPageRows = getReplacedVillageRows($(page));
  6832.                                         if (tableHandler.settings.hasBottomTotalRow) {
  6833.                                             tableHandler.overviewTable.find("tr:last").before(nextPageRows);
  6834.                                         } else {
  6835.                                             tableHandler.overviewTable.append(nextPageRows);
  6836.                                         }
  6837.            
  6838.                                         nextPageUrl.replaceWith("<strong>"+nextPageUrl.text()+"</strong>");
  6839.                                     });
  6840.                                 });
  6841.                             }
  6842.                         }
  6843.                     }
  6844.                 }
  6845.             })(tableHandler || (tableHandler = {}));
  6846.  
  6847.             // PRODUCTION OVERVIEW
  6848.             if (location.href.indexOf('mode=prod') > -1) {
  6849.                 (function() {
  6850.                     //console.time("overview-prod");
  6851.                     try {
  6852.                         overviewTable = $("#production_table");
  6853.                         tableHandler.init("production_table");
  6854.                
  6855.                         // Filter full storage rooms
  6856.                         var resTable = $("#production_table");
  6857.                         var menu = "<table class='vis' width='100%'>";
  6858.                         menu += "<tr><th width='99%'>";
  6859.                         menu += " <input type=checkbox id=resFilter " + (user_data.resources.filterRows ? "checked" : "") + "> " + trans.sp.prodOverview.filter + " ";
  6860.                         menu += "&nbsp;<input type=button id=resStorageFull value='" + trans.sp.prodOverview.filterFullGS + "' title=''>&nbsp; &nbsp; ";
  6861.                         menu += "<select id=resAmountType><option value=1>" + trans.sp.all.more + "</option>";
  6862.                         menu += "<option value=-1>" + trans.sp.all.less + "</option></select>";
  6863.                         menu += "<input type=text id=resAmount size=6 value=" + user_data.resources.requiredResDefault + ">";
  6864.                         menu += " <input type=button class=resFilter value='" + trans.tw.all.wood + "' resIndex=0><input type=button class=resFilter value='" + trans.tw.all.stone + "' resIndex=1><input type=button class=resFilter value='" + trans.tw.all.iron + "' resIndex=2><input type=button class=resFilter value='" + trans.sp.all.all + "' resIndex=-1>";
  6865.                         menu += " " + trans.sp.all.withText + " <input type=checkbox id=resMerchant " + (user_data.resources.filterMerchants ? "checked" : "") + " title='" + trans.sp.prodOverview.merchantTooltip + "'>";
  6866.                         menu += "<input type=text id=resMerchantAmount size=2 value=" + user_data.resources.requiredMerchants + " title='" + trans.sp.prodOverview.merchantAmountTooltip + "'> " + trans.sp.all.merchants + " ";
  6867.                
  6868.                         menu += "</th><th width='1%' nowrap>";
  6869.                         menu += "<input type=button id=resBBCode value='" + trans.sp.prodOverview.bbCodes + "'> "
  6870.                             + "<input type=checkbox id=resBBCodeImages> " + trans.sp.prodOverview.bbCodesInfo + "&nbsp; ";
  6871.                         menu += "</th></tr></table>";
  6872.                         resTable.before(menu);
  6873.                
  6874.                         $("#resFilter").change(function () {
  6875.                             var isCheck = $(this).is(":checked");
  6876.                             $("#resFilter").attr("title", isCheck ? trans.sp.prodOverview.filterTooltip : trans.sp.prodOverview.filterTooltipReverse);
  6877.                             $("#resStorageFull").attr("title", isCheck ? trans.sp.prodOverview.filterFullGSTooltip : trans.sp.prodOverview.filterFullGSTooltipReverse);
  6878.                             $(".resFilter").each(function (index, value) {
  6879.                                 if (index == 3) {
  6880.                                     $(value).attr("title", isCheck ? trans.sp.prodOverview.filterAllTooltip : trans.sp.prodOverview.filterAllTooltipReverse);
  6881.                                 } else {
  6882.                                     $(value).attr("title", isCheck ? trans.sp.prodOverview.filter1Tooltip.replace("{0}", $(value).attr("value")) : trans.sp.prodOverview.filter1TooltipReverse.replace("{0}", $(value).attr("value")));
  6883.                                 }
  6884.                             });
  6885.                         });
  6886.                
  6887.                         $("#resFilter").change();
  6888.                
  6889.                         $("#resStorageFull").click(function () {
  6890.                             trackClickEvent("FilterFullRes");
  6891.                             filterRes('full', $("#resFilter").is(":checked"));
  6892.                         });
  6893.                
  6894.                         $("#resBBCode").click(function () {
  6895.                             trackClickEvent("BBCodeOutput");
  6896.                             var bbs = filterRes("bbcode", false),
  6897.                                 reverse = $("#resAmountType").val() == "-1",
  6898.                                 bbCodesTitle = reverse ? trans.sp.prodOverview.tooLittleText : trans.sp.prodOverview.tooMuchText;
  6899.                
  6900.                             bbCodesTitle = bbCodesTitle
  6901.                                 .replace("{diff}", parseInt(user_data.resources.bbcodeMinimumDiff / 1000, 10))
  6902.                                 .replace("{min}", parseInt(parseInt($("#resAmount").val(), 10) / 1000, 10));
  6903.                
  6904.                             if ($("#textsArea").size() == 0) {
  6905.                                 $(this).parent().parent().parent().append("<tr><td colspan=2 id=textsArea></td></tr>");
  6906.                             } else {
  6907.                                 $("#textsArea").html("");
  6908.                             }
  6909.                
  6910.                             $("#textsArea").append("<b>" + bbCodesTitle + "</b>" + trans.sp.prodOverview.bbCodeExtraInfo + "<br><textarea id=bbcodeArea cols=50 rows=10 wrap=off>");
  6911.                             $("#bbcodeArea").val(bbs);
  6912.                
  6913.                             $("#textsArea").append("<br><input type=button value='" + trans.sp.all.close + "' id=closeTextsArea>");
  6914.                             $("#closeTextsArea").click(function() {
  6915.                                 $("#textsArea").parent().remove();
  6916.                             });
  6917.                         });
  6918.                
  6919.                         function filterRes(resourceIndex, hideRows) {
  6920.                             var resCode = [trans.tw.all.wood, trans.tw.all.stone, trans.tw.all.iron];
  6921.                             var bbcodes = '';
  6922.                             var goners = $();
  6923.                             var stayers = $();
  6924.                             var filterMerchants = $("#resMerchant").is(":checked");
  6925.                             var filterMerchantsAmount = parseInt($("#resMerchantAmount").val(), 10);
  6926.                             var minAmount = parseInt($("#resAmount").val(), 10);
  6927.                             var reverse = $("#resAmountType").val() == "-1";
  6928.                             var bbCodeImages = $("#resBBCodeImages").is(":checked");
  6929.                             var minDif = user_data.resources.bbcodeMinimumDiff;
  6930.                             var bbCodesTitle;
  6931.                
  6932.                             if (reverse) {
  6933.                                 bbcodes = trans.sp.prodOverview.tooLittle + "\n";
  6934.                                 bbCodesTitle = trans.sp.prodOverview.tooLittleText;
  6935.                             } else {
  6936.                                 bbcodes = trans.sp.prodOverview.tooMuch + "\n";
  6937.                                 bbCodesTitle = trans.sp.prodOverview.tooMuchText;
  6938.                             }
  6939.                             bbCodesTitle = bbCodesTitle.replace("{min}", minDif).replace("{diff}", minAmount);
  6940.                
  6941.                             function doResource(resCell, resArray, resIndex, reverse, minAmount) {
  6942.                                 var resAmount = parseInt(resArray[resIndex], 10);
  6943.                                 if ((!reverse && resAmount > minAmount) || (reverse && resAmount < minAmount)) {
  6944.                                     $("span[title]:eq(" + resIndex + ")", resCell).css("font-weight", "bold")
  6945.                                     return false;
  6946.                                 }
  6947.                                 return true;
  6948.                             }
  6949.                
  6950.                             var hasNotes = $("th:first", resTable).text().indexOf(trans.tw.overview.village) == -1;
  6951.                             resTable.find("tr:gt(0)").each(function () {
  6952.                                 var isOk = true;
  6953.                                 var resCell;
  6954.                                 if (hasNotes) {
  6955.                                     resCell = $(this).find("td:eq(3)");
  6956.                                 } else {
  6957.                                     resCell = $(this).find("td:eq(2)");
  6958.                                 }
  6959.                                 var resources = $.trim(resCell.text()).replace(/\./gi, "").split(" ");
  6960.                
  6961.                                 if (resourceIndex == 'bbcode') {
  6962.                                     // All resources
  6963.                                     var villageBBCode = '';
  6964.                                     for (var i = 0; i < 3; i++) {
  6965.                                         if ((!reverse && resources[i] - minDif > minAmount) || (reverse && parseInt(resources[i], 10) + parseInt(minDif, 10) < minAmount)) {
  6966.                                             if (bbCodeImages) {
  6967.                                                 villageBBCode += "[img]https://www.tribalwars.nl/graphic/" + world_data.resources[i] + ".png[/img] ";
  6968.                                             } else {
  6969.                                                 villageBBCode += resCode[i] + " ";
  6970.                                             }
  6971.                                             villageBBCode += parseInt(Math.abs(resources[i] - minAmount) / 1000, 10) + "k ";
  6972.                                         }
  6973.                                     }
  6974.                                     if (villageBBCode.length > 0) {
  6975.                                         var villageCell = $("td:eq(" + (hasNotes ? "1" : "0") + ") span:eq(1)", this);
  6976.                                         bbcodes += "[village]" + getVillageFromCoords(villageCell.text()).coord + "[/village] " + villageBBCode + "\n";
  6977.                                     }
  6978.                                 } else if (resourceIndex == 'full') {
  6979.                                     // full storage rooms
  6980.                                     if ($(".warn", this).size() > 0) {
  6981.                                         resCell.css("background-color", user_data.resources.highlightColor);
  6982.                                         isOk = false;
  6983.                                     }
  6984.                
  6985.                                 } else {
  6986.                                     // One specific resource
  6987.                                     $("span[title]", resCell).css("font-weight", "normal");
  6988.                
  6989.                                     if (resourceIndex == "-1") {
  6990.                                         isOk = isOk && !(!doResource(resCell, resources, 0, reverse, minAmount)
  6991.                                             | !doResource(resCell, resources, 1, reverse, minAmount)
  6992.                                             | !doResource(resCell, resources, 2, reverse, minAmount));
  6993.                                     } else {
  6994.                                         isOk = isOk && doResource(resCell, resources, resourceIndex, reverse, minAmount);
  6995.                                     }
  6996.                
  6997.                                     if (!isOk) {
  6998.                                         resCell.css("background-color", user_data.resources.highlightColor);
  6999.                                     } else {
  7000.                                         resCell.css("background-color", "");
  7001.                                     }
  7002.                
  7003.                                     if (filterMerchants) {
  7004.                                         resCell = $(this).find("td:eq(4)");
  7005.                                         if (hasNotes) {
  7006.                                             resCell = resCell.next();
  7007.                                         }
  7008.                                         var merchants = resCell.text();
  7009.                                         merchants = merchants.substr(0, merchants.indexOf("/"));
  7010.                                         if (merchants < filterMerchantsAmount) {
  7011.                                             resCell.css("background-color", user_data.colors.error);
  7012.                                         } else {
  7013.                                             resCell.css("background-color", "");
  7014.                                         }
  7015.                                     }
  7016.                                 }
  7017.                
  7018.                                 if (hideRows && isOk) {
  7019.                                     goners = goners.add($(this));
  7020.                
  7021.                                     // Village rename script will not rename villages if the hidden rename inputfield is on a hidden row
  7022.                                     // --> People were using our script to filter the village list and then use a mass village renamer which also renamed the hidden village rows
  7023.                                     $("input:first", $(this)).val("");
  7024.                
  7025.                                 }
  7026.                                 else if (!$(this).is(":visible")) {
  7027.                                     stayers = stayers.add($(this));
  7028.                                 }
  7029.                             });
  7030.                             if (hideRows) {
  7031.                                 goners.remove();
  7032.                                 var amountOfVillagesCell = $("tr:first th", resTable).eq(hasNotes ? 1 : 0);
  7033.                                 amountOfVillagesCell.text(amountOfVillagesCell.text().replace(/\d+/, $("tr", resTable).size() - 1));
  7034.                             } else {
  7035.                                 stayers.show();
  7036.                                 goners.hide();
  7037.                             }
  7038.                
  7039.                             return bbcodes;
  7040.                         }
  7041.                
  7042.                         $(".resFilter").click(function () {
  7043.                             trackClickEvent("FilterResource");
  7044.                             filterRes($(this).attr("resIndex"), $("#resFilter").is(":checked"));
  7045.                         });
  7046.                     } catch (e) { handleException(e, "overview-prod"); }
  7047.                     //console.timeEnd("overview-prod");
  7048.                 }());
  7049.             }
  7050.             // TROOPS OVERVIEW
  7051.             else if (location.href.indexOf('mode=units') > -1
  7052.                         && (location.href.indexOf('type=own_home') > -1 || location.href.indexOf('type=there') > -1)) {
  7053.                 (function() {
  7054.                     //console.time("overview-thereownhome");
  7055.                     try {
  7056.                         var villageCounter = 0;
  7057.                         var rowSize = world_data.units.length + 1;
  7058.                         if (world_config.hasMilitia) {
  7059.                             rowSize++;
  7060.                         }
  7061.                
  7062.                         var overviewMenuRowFilter = "tr:gt(0)",
  7063.                             /**
  7064.                              * Page speed can be overruled by the querystring
  7065.                              */
  7066.                             currentPageSpeed = spSpeedCookie(),
  7067.                             /**
  7068.                              * {village} object from target cookie (or by url querystring set)
  7069.                              */
  7070.                             target;
  7071.                
  7072.                         /**
  7073.                          * Do initial filter? (based on querystring)
  7074.                          */
  7075.                         var doFilter = false,
  7076.                             unitIndex = world_data.units.indexOf(user_data.command.filterMinDefaultType),
  7077.                             unitAmount = user_data.command.filterMinDefault,
  7078.                             sort = false,
  7079.                             changeSpeed = false,
  7080.                             i;
  7081.                         var search = window.location.search.substring(1).split("&");
  7082.                         for (i = 0; i < search.length; i++) {
  7083.                             var item = search[i].split("=");
  7084.                             switch (item[0]) {
  7085.                                 case 'unit':
  7086.                                     doFilter = true;
  7087.                                     unitIndex = world_data.units.indexOf(item[1]);
  7088.                                     break;
  7089.                                 case 'amount':
  7090.                                     doFilter = true;
  7091.                                     unitAmount = parseInt(item[1], 10);
  7092.                                     break;
  7093.                                 case 'changeSpeed':
  7094.                                     changeSpeed = item[1];
  7095.                                     if (changeSpeed != false) {
  7096.                                         //spSpeedCookie(changeSpeed);
  7097.                                         currentPageSpeed = changeSpeed;
  7098.                                     }
  7099.                                     break;
  7100.                        
  7101.                                 case 'targetvillage':
  7102.                                     var newTargetVillage = getVillageFromCoords(item[1]);
  7103.                                     spTargetVillageCookie(newTargetVillage.coord);
  7104.                                     break;
  7105.                        
  7106.                                 case 'sort':
  7107.                                     sort = item[1] == "true";
  7108.                                     break;
  7109.                             }
  7110.                         }
  7111.                
  7112.                         // Sangu package menu is also built in reinitialize_table
  7113.                         /**
  7114.                          * Creates a select box with all unit types in this world
  7115.                          * @param {string} id the DOM ID
  7116.                          * @param {string} select the currently selected unit
  7117.                          */
  7118.                         function makeUnitBox(id, select) {
  7119.                             var box = "<select id=" + id + ">";
  7120.                             $.each(world_data.units, function (i, v) {
  7121.                                 box += "<option value=" + i + (v == select ? " selected" : "") + ">" + trans.tw.units.names[v] + "</option>";
  7122.                             });
  7123.                             box += "</select>";
  7124.                             return box;
  7125.                         }
  7126.                        
  7127.                         var menu = "<table width='100%' class='vis'>";
  7128.                         menu += "<tr>";
  7129.                         menu += "<th nowrap width='1%'>";
  7130.                         menu += "<input type=text size=5 id=filterAxeValue value='" + user_data.command.filterMinDefault + "'>";
  7131.                         menu += makeUnitBox("filterAxeType", user_data.command.filterMinDefaultType);
  7132.                         menu += "<input type=button id=filterAxe value='" + trans.sp.troopOverview.filterTroops + "'";
  7133.                         menu += " title='" + trans.sp.troopOverview.filterTroopsTooltip + "'> ";
  7134.                        
  7135.                         menu += "</th><th nowrap width='1%'>";
  7136.                        
  7137.                         menu += "<select id=filterPopValueType><option value=1>" + trans.sp.all.more + "</option>";
  7138.                         menu += "<option value=-1>" + trans.sp.all.less + "</option></select>";
  7139.                         menu += "<input type=text size=5 id=filterPopValue value='" + user_data.command.filterMinPopulation + "'>";
  7140.                         menu += "<input type=button id=filterPop value='" + trans.sp.troopOverview.filterPopulation + "' title='" + trans.sp.troopOverview.filterPopulationTooltip + "'> ";
  7141.                        
  7142.                         menu += "</th><th nowrap width='1%'>";
  7143.                         menu += "<input type=text size=5 id=filterWalkingTimeValue>";
  7144.                         menu += "<input type=button id=filterWalkingTime value='" + trans.sp.troopOverview.filterWalkingTime + "' title='" + trans.sp.troopOverview.filterWalkingTimeTooltip + "'> ";
  7145.                        
  7146.                         menu += "</th><th width='95%'>";
  7147.                        
  7148.                         menu += "<input type=button id=snobFilter value='" + trans.sp.troopOverview.filterNoble + "' title='" + trans.sp.troopOverview.filterNobleTooltip + "'> &nbsp; ";
  7149.                         menu += "<input type=button id=attackFilter value='" + trans.sp.troopOverview.filterUnderAttack + "' title='" + trans.sp.troopOverview.filterUnderAttackTooltip + "'> &nbsp; ";
  7150.                        
  7151.                         menu += "</th><th width='1%'>";
  7152.                        
  7153.                         menu += "<input type=button id=calculateStack value='" + trans.sp.troopOverview.calcStack + "' title='" + trans.sp.troopOverview.calcStackTooltip + "'>";
  7154.                         menu += "</th>";
  7155.                        
  7156.                         menu += "</tr></table>";
  7157.                        
  7158.                         // second row
  7159.                         menu += "<table><tr><th width='1%' nowrap>";
  7160.                         menu += "<input type=checkbox id=defReverseFilter title='" + trans.sp.commands.filtersReverse + "'> " + trans.sp.commands.filtersReverseInfo + ": ";
  7161.                        
  7162.                         menu += "</th><th width='1%' nowrap>";
  7163.                         menu += "<input type=text size=12 id=defFilterTextValue value=''>";
  7164.                         menu += "<input type=button id=defFilterText value='" + trans.sp.commands.freeTextFilter + "'>";
  7165.                        
  7166.                         menu += "</th><th width='97%' nowrap>";
  7167.                         menu += "<input type=textbox size=3 id=defFilterContinentText maxlength=2><input type=button id=defFilterContinent value='" + trans.sp.commands.continentFilter + "'>";
  7168.                        
  7169.                         menu += "</th>";
  7170.                        
  7171.                         if (location.href.indexOf('type=there') > -1) {
  7172.                             menu += "<th width='1%'><input type=button id=defRestack value='" + trans.sp.troopOverview.restack + "'></th>";
  7173.                         }
  7174.                         menu += "<th nowrap width='1%' style='padding-right: 8px; padding-top: 3px;'>";
  7175.                        
  7176.                         menu += "<input type=checkbox id=sortIt title='" + trans.sp.troopOverview.sortTooltip + "'"
  7177.                             + (user_data.command.filterAutoSort ? " checked" : "") + "> "
  7178.                             + trans.sp.troopOverview.sort;
  7179.                        
  7180.                         menu += "</th></tr>";
  7181.                         menu += "</table>";
  7182.                        
  7183.                         // Sangu filter menu
  7184.                         var sanguMenu = menu;
  7185.                        
  7186.                         // Overview table menu
  7187.                         menu = "<tr id=units_table_header>";
  7188.                         menu += "<th>" + trans.sp.troopOverview.village + "</th>";
  7189.                         menu += "<th>" + trans.sp.troopOverview.nightBonus + "</th>";
  7190.                         $.each(world_data.units, function (i, v) {
  7191.                             menu += "<th><img src='/graphic/unit/unit_" + v + ".png' title=\"" + trans.sp.troopOverview.selectUnitSpeed.replace("{0}", trans.tw.units.names[v]) + "\" alt='' id=" + v + " /></th>";
  7192.                         });
  7193.                         if (world_config.hasMilitia) {
  7194.                             menu += "<th><img src='/graphic/unit/unit_militia.png' title='" + trans.tw.units.militia + "' alt='' id=militia /></th>";
  7195.                         }
  7196.                         menu += "<th>" + trans.sp.troopOverview.commandTitle + "</th>";
  7197.                        
  7198.                        
  7199.                         target = getVillageFromCoords(spTargetVillageCookie());
  7200.                         menu += "<th nowrap>" + trans.sp.all.targetEx
  7201.                             + " <input type=text id=targetVillage name=targetVillage size=8 value='"
  7202.                             + (target.isValid ? target.coord : "") + "'>"
  7203.                             + "<input type=button class='btn' id=targetVillageButton value='"
  7204.                             + trans.sp.troopOverview.setTargetVillageButton + "'></th>";
  7205.                         menu += "</tr>";
  7206.                        
  7207.                        
  7208.                        
  7209.                        
  7210.                        
  7211.                        
  7212.                         // function to replace the village rows
  7213.                         tableHandler.init("units_table", {
  7214.                             rowReplacer: function (row) {
  7215.                                 //q($(row).html());
  7216.                                 var mod = "row_a";
  7217.                                 var newRow = "";
  7218.                                 var finalRow = "";
  7219.                                 var addThisRow = true;
  7220.                                 var cells = $("td:gt(0)", row);
  7221.                                 var units = {};
  7222.                                 var villageCell = $("td:first", row);
  7223.                                 var villageId = $("span.quickedit-vn", villageCell).attr("data-id");
  7224.                        
  7225.                                 cells.each(function (index, element) {
  7226.                                     if (doFilter && index - 1 == unitIndex && parseInt(this.innerHTML, 10) < unitAmount) {
  7227.                                         //q("index:" + index + ' == '+ unitIndex + " : " + row.html() + ' * 1 < ' + unitAmount);
  7228.                                         addThisRow = false;
  7229.                                         return false;
  7230.                                     }
  7231.                                     else if (index == rowSize) {
  7232.                                         //q(index + "==" + rowSize);
  7233.                                         newRow += "<td>";
  7234.                                         newRow += "<img src='/graphic/dots/red.png' title='" + trans.sp.troopOverview.removeVillage + "' style='margin-bottom: 2px' /> ";
  7235.                                         //newRow += "<img src='https://www.tribalwars.vodka/graphic/delete_small.png' style='margin-bottom: 3px; position: relative' title='" + trans.sp.troopOverview.removeVillage + "' /> ";
  7236.                                         newRow += "<a href='" + $("a", element).attr('href').replace("mode=units", "") + "&sanguX=0&sanguY=0' class='attackLinks'>";
  7237.                                         newRow += "<img src='/graphic/command/attack.png' title='" + trans.sp.troopOverview.toThePlace + "' style='margin-bottom: 1px' />";
  7238.                                         // Works only with leftclick onclick='this.src=\"/graphic/command/return.png\";'
  7239.                                         newRow += "</a>";
  7240.                                         newRow += "</td>";
  7241.                                     } else {
  7242.                                         //q("units:" + world_data.units[index - 1]);
  7243.                                         var cellDisplay = this.innerHTML;
  7244.                                         if (cellDisplay === "0") {
  7245.                                             cellDisplay = "&nbsp;";
  7246.                                         }
  7247.                                         else if (cellDisplay.indexOf('="has_tooltip"') > -1)  {
  7248.                                             cellDisplay = cellDisplay.replace('="has_tooltip"', '="has_tooltip" title="'+trans.sp.troopOverview.cheapNobles+'"');
  7249.                                         }
  7250.                        
  7251.                                         newRow += "<td>" + cellDisplay + "</td>";
  7252.                                         if (index > 0) {
  7253.                                             units[world_data.units[index - 1]] = parseInt(element.innerHTML, 10);
  7254.                                         }
  7255.                                         // innerHTML can contain a + sign for the nobles: "+" indicates nobles can be rebuild cheaply
  7256.                                         // The snobs are not important here
  7257.                                     }
  7258.                                 });
  7259.                        
  7260.                                 if (addThisRow) {
  7261.                                     var villageType = calcTroops(units);
  7262.                                     if (doFilter) {
  7263.                                         mod = villageCounter % 2 == 0 ? "row_a" : "row_b";
  7264.                                     } else {
  7265.                                         mod = !villageType.isDef ? "row_a" : "row_b";
  7266.                                     }
  7267.                        
  7268.                                     var coord = getVillageFromCoords(villageCell.text());
  7269.                        
  7270.                                     //finalRow += "<tbody>";
  7271.                                     finalRow += "<tr arrival='0' data-coord-x='" + coord.x + "' data-coord-y='" + coord.y + "' "
  7272.                                         + " class='row_marker " + mod + (game_data.village.id == villageId ? " selected" : "") + "'>";
  7273.                                     finalRow += "<td>" + villageCell.html() + "</td>";
  7274.                                     finalRow += newRow;
  7275.                                     finalRow += "<td></td></tr>";
  7276.                                     //finalRow += "</tbody>";
  7277.                        
  7278.                                     villageCounter++;
  7279.                        
  7280.                                     return finalRow;
  7281.                                 }
  7282.                                 return "";
  7283.                             }
  7284.                         });
  7285.                        
  7286.                         var newTable = tableHandler.getReplacedVillageRows();
  7287.                         $("#units_table")
  7288.                             .html("<table width='100%' class='vis' id='units_table' target='false'>" + menu + newTable + "</table>")
  7289.                             .before(sanguMenu);
  7290.                        
  7291.                        
  7292.                         // Tooltips
  7293.                         $("#defReverseFilter").change( function () {
  7294.                             var isChecked = $(this).is(":checked");
  7295.                             var defTrans = trans.sp.troopOverview;
  7296.                        
  7297.                             $("#defFilterContinent").attr("title", isChecked ? defTrans.continentFilterTooltip : defTrans.continentFilterTooltipReverse);
  7298.                             $("#defFilterText").attr("title", defTrans.freeTextFilterTooltip.replace("{filterType}", isChecked ? defTrans.freeTextFilterTooltipFilterTypeWith : defTrans.freeTextFilterTooltipFilterTypeWithout));
  7299.                         });
  7300.                         $("#defReverseFilter").change();
  7301.                        
  7302.                        
  7303.                         // Initial focus on target inputbox
  7304.                         $('#targetVillage').click(function () {
  7305.                             $(this).focus().select();
  7306.                         });
  7307.                        
  7308.                        
  7309.                        
  7310.                         // "Attacks per page" -> change to # villages in the list
  7311.                         var pageSize = $("input[name='page_size']");
  7312.                         var villageAmountCell = $("#units_table tr:first th:first");
  7313.                         //assert(villageAmountCell.length === 1, "village cell Dorp (xxx) niet gevonden");
  7314.                         villageAmountCell.text(villageAmountCell.text() + " (0)");
  7315.                         function setVillageCount(amount) {
  7316.                             pageSize.val(amount);
  7317.                             villageAmountCell.text(villageAmountCell.text().replace(/\d+/, amount));
  7318.                         }
  7319.                        
  7320.                         pageSize.parent().prev().text(trans.sp.overviews.totalVillages);
  7321.                         setVillageCount(villageCounter);
  7322.                
  7323.                         // Recalculate arrival times as the target village changes
  7324.                         $("#targetVillageButton").click(function () {
  7325.                             trackClickEvent("TargetVillageSet");
  7326.                             var targetMatch = getVillageFromCoords($('#targetVillage').val(), true);
  7327.                             $("#units_table").attr("target", targetMatch.isValid);
  7328.                             if (!targetMatch.isValid) {
  7329.                                 spTargetVillageCookie("");
  7330.                                 alert(trans.sp.troopOverview.setTargetVillageButtonAlert);
  7331.                                 $("#targetVillage").focus();
  7332.                        
  7333.                             } else {
  7334.                                 $(".attackLinks", tableHandler.overviewTable).each(function() {
  7335.                                     // add target coordinates to attack image href which are read in place
  7336.                                     var hrefWithVillageCoords = $(this).attr("href");
  7337.                                     hrefWithVillageCoords = hrefWithVillageCoords.replace(/sanguX=(\d+)&sanguY=(\d+)/, "sanguX="+targetMatch.x+"&sanguY="+targetMatch.y);
  7338.                                     $(this).attr("href", hrefWithVillageCoords);
  7339.                                 });
  7340.                        
  7341.                                 spTargetVillageCookie(targetMatch.coord);
  7342.                                 $("#units_table").find(overviewMenuRowFilter).each(function () {
  7343.                                     var unitRow = $(this),
  7344.                                         dist = getDistance(targetMatch.x, unitRow.attr("data-coord-x"), targetMatch.y, unitRow.attr("data-coord-y"), currentPageSpeed);
  7345.                        
  7346.                                     $("td:last", unitRow).html(dist.html);
  7347.                                     $(this).attr("arrival", dist.travelTime);
  7348.                                     if (dist.isNightBonus) {
  7349.                                         $("td:eq(1)", unitRow).css("background-color", user_data.colors.error);
  7350.                                     } else {
  7351.                                         $("td:eq(1)", unitRow).css("background-color", '');
  7352.                                     }
  7353.                                 });
  7354.                        
  7355.                                 if ($("#sortIt").is(":checked")) {
  7356.                                     $("#units_table").find(overviewMenuRowFilter).sortElements(function (a, b) {
  7357.                                         return parseInt($(a).attr("arrival"), 10) > parseInt($(b).attr("arrival"), 10) ? 1 : -1;
  7358.                                     });
  7359.                                 }
  7360.                             }
  7361.                         });
  7362.                        
  7363.                         // sort can be set with the querystring
  7364.                         if (sort) {
  7365.                             $("#targetVillageButton").click();
  7366.                         }
  7367.                        
  7368.                         // delete a table row
  7369.                         $("#units_table").mouseup(function (e) {
  7370.                             if (e.target.nodeName === 'IMG') {
  7371.                                 if (e.target.title == trans.sp.troopOverview.removeVillage) {
  7372.                                     setVillageCount(parseInt(pageSize.val(), 10) - 1);
  7373.                                     $(e.target).parent().parent().remove();
  7374.                                 }
  7375.                             }
  7376.                         });
  7377.                        
  7378.                         // remove row or add border to command cell when middle mouse click (open in new tab)
  7379.                         $(tableHandler.overviewTable).on("mousedown",".attackLinks", function(e) {
  7380.                             if (e.which == 2) {
  7381.                                 var cell = $(e.target).parent().parent();
  7382.                                 if (user_data.command.middleMouseClickDeletesRow2) {
  7383.                                     cell.parent().remove();
  7384.                                 } else {
  7385.                                     cell.css("border", (parseInt(cell.css("border-width").substr(0, 1), 10) + 1) + "px red solid");
  7386.                                 }
  7387.                             }
  7388.                         });
  7389.                        
  7390.                         // Change active speed by clicking on a unit icon
  7391.                         // ATTN: border style duplicated in trans.troopOverview.help
  7392.                         $('#' + currentPageSpeed).parent().css("border", "2px green dotted");
  7393.                         $('#' + spSpeedCookie()).parent().css("border", "3px red solid");
  7394.                         $("#units_table_header").click(function (e) {
  7395.                             if (e.target.nodeName === 'IMG' && e.target.id !== "militia") {
  7396.                                 currentPageSpeed = e.target.id;
  7397.                                 $("img", this).parent().css("border", "0px");
  7398.                                 $('#' + currentPageSpeed).parent().css("border", "2px green dotted");
  7399.                                 $('#' + spSpeedCookie()).parent().css("border", "3px red solid");
  7400.                                 $("#targetVillageButton").click();
  7401.                             }
  7402.                         });
  7403.                        
  7404.                         $("#units_table_header").dblclick(function (e) {
  7405.                             if (e.target.nodeName === 'IMG' && e.target.id !== "militia") {
  7406.                                 currentPageSpeed = e.target.id;
  7407.                                 spSpeedCookie(e.target.id);
  7408.                                 $("img", this).parent().css("border", "0px");
  7409.                                 $('#' + currentPageSpeed).parent().css("border", "2px green dotted");
  7410.                                 $('#' + spSpeedCookie()).parent().css("border", "3px red solid");
  7411.                                 $("#targetVillageButton").click();
  7412.                             }
  7413.                         });
  7414.                         function filterVillageRows(filterStrategy, options) {
  7415.                             // return true to hidethe row; false keep row visible (without reverse filter checkbox)
  7416.                             options = options || {
  7417.                                     allowFilter: true
  7418.                                 };
  7419.                             var reverseFilter = options.allowFilter && $("#defReverseFilter").is(":checked"),
  7420.                                 goners = $(),
  7421.                                 villageCounter = 0;
  7422.                        
  7423.                             $("#units_table").find(overviewMenuRowFilter).each(function () {
  7424.                                 var self = $(this);
  7425.                                 if (!reverseFilter != !filterStrategy(self)) {
  7426.                                     goners = goners.add(self);
  7427.                                     $("input:eq(1)", self).val("");
  7428.                                 } else {
  7429.                                     villageCounter++;
  7430.                                 }
  7431.                             });
  7432.                             goners.remove();
  7433.                        
  7434.                             // Show totals
  7435.                             setVillageCount(villageCounter);
  7436.                         }
  7437.                        
  7438.                         // CONTINENT FILTER
  7439.                         $("#defFilterContinent").click(function () {
  7440.                             trackClickEvent("FilterContinent");
  7441.                             var continent = parseInt($("#defFilterContinentText").val(), 10);
  7442.                             if (!isNaN(continent)) {
  7443.                                 filterVillageRows(function (row) {
  7444.                                     var village = getVillageFromCoords(row.find("td:first").text());
  7445.                                     if (!village.isValid ) {
  7446.                                         return true;
  7447.                                     }
  7448.                                     return village.continent() != continent;
  7449.                                 });
  7450.                             }
  7451.                         });
  7452.                        
  7453.                         // TEXT FILTER
  7454.                         $("#defFilterText").click(function () {
  7455.                             trackClickEvent("FilterText");
  7456.                             var compareTo = $("#defFilterTextValue").val().toLowerCase();
  7457.                             if (compareTo.length > 0) {
  7458.                                 filterVillageRows(function (row) {
  7459.                                     return row.text().toLowerCase().indexOf(compareTo) == -1;
  7460.                                 });
  7461.                             }
  7462.                         });
  7463.                        
  7464.                         // WALKINGTIME FILTER
  7465.                         $("#filterWalkingTime").click(function () {
  7466.                             var minWalkingTime = parseInt($("#filterWalkingTimeValue").val(), 10) * 60;
  7467.                        
  7468.                             if (!isNaN(minWalkingTime)) {
  7469.                                 trackClickEvent("WalkingTime");
  7470.                                 filterVillageRows(
  7471.                                     function (row) {
  7472.                                         return parseInt(row.attr("arrival"), 10) < minWalkingTime;
  7473.                                     },
  7474.                                     {
  7475.                                         allowFilter: false
  7476.                                     }
  7477.                                 );
  7478.                             } else {
  7479.                                 alert(trans.sp.troopOverview.filterWalkingTimeTooltip);
  7480.                             }
  7481.                         });
  7482.                        
  7483.                        
  7484.                        
  7485.                        
  7486.                        
  7487.                        
  7488.                         // Filter rows with less than x axemen (or another unit)
  7489.                         $("#filterAxe").click(function () {
  7490.                             trackClickEvent("FilterUnitAmount");
  7491.                             var villageCounter = 0;
  7492.                             var goners = $();
  7493.                             var minAxeValue = parseInt($("#filterAxeValue").val(), 10);
  7494.                             var unit = parseInt($('#filterAxeType').val(), 10);
  7495.                             $("#units_table").find(overviewMenuRowFilter).each(function () {
  7496.                                 var val = $("td:eq(" + (unit + 2) + ")", this).html();
  7497.                                 if (val == '&nbsp;' || parseInt(val, 10) < minAxeValue) {
  7498.                                     goners = goners.add($(this));
  7499.                                     $("input:first", $(this)).val("");
  7500.                                 }
  7501.                                 else
  7502.                                     villageCounter++;
  7503.                             });
  7504.                             goners.remove();
  7505.                             setVillageCount(villageCounter);
  7506.                         });
  7507.                         // change by default selected unit the filter will be active for
  7508.                         $("#filterAxeType").change(function () {
  7509.                             var unit = world_data.units[$(this).val()];
  7510.                             if (typeof user_data.command.filterMin[unit] !== 'undefined') {
  7511.                                 $("#filterAxeValue").val(user_data.command.filterMin[unit]);
  7512.                             } else {
  7513.                                 $("#filterAxeValue").val(user_data.command.filterMinOther);
  7514.                             }
  7515.                         });
  7516.                        
  7517.                        
  7518.                        
  7519.                         // Filter rows without snobs/nobles
  7520.                         $("#snobFilter").click(function () {
  7521.                             trackClickEvent("FilterSnob");
  7522.                             var villageCounter = 0;
  7523.                             var goners = $();
  7524.                             $("#units_table").find(overviewMenuRowFilter).each(function () {
  7525.                                 if ($.trim($("td:eq(" + (world_data.unitsPositionSize.length + 1) + ")", this).text()) === '') {
  7526.                                     goners = goners.add($(this));
  7527.                                     $("input:first", $(this)).val("");
  7528.                                 } else
  7529.                                     villageCounter++;
  7530.                             });
  7531.                             goners.remove();
  7532.                             setVillageCount(villageCounter);
  7533.                         });
  7534.                        
  7535.                         // hide rows not under attack
  7536.                         $("#attackFilter").click(function () {
  7537.                             trackClickEvent("FilterUnderAttack");
  7538.                             var villageCounter = 0;
  7539.                             var goners = $();
  7540.                             $("#units_table").find(overviewMenuRowFilter).each(function () {
  7541.                                 //q("'" + $(this).html() + "'");
  7542.                                 //q("---------------------------------------------------");
  7543.                                 if ($('td:first:not(:has(img[title=\'' + trans.tw.command.attack + '\']))', this).size() != 0) {
  7544.                                     goners = goners.add($(this));
  7545.                                     $("input:first", $(this)).val("");
  7546.                                 } else {
  7547.                                     villageCounter++;
  7548.                                 }
  7549.                             });
  7550.                             goners.remove();
  7551.                             setVillageCount(villageCounter);
  7552.                         });
  7553.                        
  7554.                         // filter rows with less then x population
  7555.                         $("#filterPop").click(function () {
  7556.                             trackClickEvent("FilterFarm");
  7557.                             $("#calculateStack").click();
  7558.                             var villageCounter = 0;
  7559.                             var goners = $();
  7560.                             var min = parseInt($("#filterPopValue").val(), 10);
  7561.                             var reverseFilter = $("#filterPopValueType").val() == "-1";
  7562.                             $("#units_table").find(overviewMenuRowFilter).each(function () {
  7563.                                 var line = $(this);
  7564.                                 $("td:eq(1)", this).each(function () {
  7565.                                     var amount = parseInt($(this).text().replace('.', ''), 10);
  7566.                                     if ((!reverseFilter && amount < min) || (reverseFilter && amount > min)) {
  7567.                                         goners = goners.add(line);
  7568.                                         $("input:first", line).val("");
  7569.                                     }
  7570.                                     else villageCounter++;
  7571.                                 });
  7572.                             });
  7573.                             goners.remove();
  7574.                             setVillageCount(villageCounter);
  7575.                         });
  7576.                         // One time help display
  7577.                         (function() {
  7578.                             if ($("#targetVillageButton").length === 0) {
  7579.                                 // group without villages
  7580.                                 return;
  7581.                             }
  7582.                        
  7583.                             var position = $("#targetVillageButton").position(),
  7584.                                 options = {
  7585.                                     left: position.left - 300,
  7586.                                     top: position.top + 35
  7587.                                 },
  7588.                                 content = {
  7589.                                     title: trans.sp.troopOverview.helpTitle,
  7590.                                     body: trans.sp.troopOverview.help.replace("{unitIcon}", "<img src='graphic/unit/unit_ram.png'>, <img src='graphic/unit/unit_spear.png'>, ...")
  7591.                                 };
  7592.                        
  7593.                             createFixedTooltip("troopOverviewTooltip", content, options);
  7594.                         }());
  7595.                        
  7596.                         // Calculate stack
  7597.                         $("#calculateStack").click(function () {
  7598.                             trackClickEvent("CalculateStack");
  7599.                             if (!this.disabled) {
  7600.                                 this.disabled = true;
  7601.                                 $("#units_table").find(overviewMenuRowFilter).each(function () {
  7602.                                     var total = 0;
  7603.                                     $("td:gt(1)", this).each(function (i) {
  7604.                                         if (!($.trim(this.innerHTML) == '' || this.innerHTML == '&nbsp;' || i >= world_data.unitsPositionSize.length)) {
  7605.                                             total += this.innerHTML * world_data.unitsPositionSize[i];
  7606.                                         }
  7607.                                     });
  7608.                                     $("td:eq(1)", this).text(formatNumber(total)).css("background-color", getStackColor(total));
  7609.                                 });
  7610.                             }
  7611.                         });
  7612.                         // Calculate Restack BB codes
  7613.                         if (location.href.indexOf('type=there') > -1) {
  7614.                             $("#defRestack").click(function () {
  7615.                                 trackClickEvent("BBCodeOutput");
  7616.                                 $("#calculateStack").click();
  7617.                        
  7618.                                 var request = "";
  7619.                                 $("#units_table").find(overviewMenuRowFilter).each(function () {
  7620.                                     var total = parseInt($("td:eq(1)", $(this)).text().replace(/\./, ''), 10);
  7621.                                     if (user_data.restack.to - total > user_data.restack.requiredDifference) {
  7622.                                         var villageDesc = $(this).find("td:first span[data-text]").text(),
  7623.                                             villageCoord = getVillageFromCoords(villageDesc);
  7624.                        
  7625.                                         request += "[village]" + villageCoord.coord + "[/village] (" + parseInt((user_data.restack.to - total) / 1000, 10) + "k)\n";
  7626.                                     }
  7627.                                 });
  7628.                        
  7629.                                 if ($("#textsArea").size() == 0) {
  7630.                                     $(this).parent().parent().parent().append("<tr><td id=textsArea colspan=5></td></tr>");
  7631.                                 } else {
  7632.                                     $("#textsArea").html("");
  7633.                                 }
  7634.                        
  7635.                                 var title = trans.sp.troopOverview.restackTitle
  7636.                                     .replace("{to}", parseInt(user_data.restack.to / 1000, 10))
  7637.                                     .replace("{requiredDiff}", parseInt(user_data.restack.requiredDifference / 1000, 10));
  7638.                                 $("#textsArea").append(title + "<br><textarea cols=35 rows=10 id=defRestackArea>" + request + "</textarea>");
  7639.                        
  7640.                                 $("#textsArea").append("<br><input type=button value='" + trans.sp.all.close + "' id=closeTextsArea>");
  7641.                                 $("#closeTextsArea").click(function() {
  7642.                                     $("#textsArea").parent().remove();
  7643.                                 });
  7644.                             });
  7645.                         }
  7646.                
  7647.                     } catch (e) { handleException(e, "overview-thereownhome"); }
  7648.                     //console.timeEnd("overview-thereownhome");
  7649.                 }());
  7650.             }
  7651.             // BUILDINGS OVERVIEW
  7652.             else if (location.href.indexOf('mode=buildings') > -1) {
  7653.                 (function() {
  7654.                     //console.time("overview-buildings");
  7655.                     try {
  7656.                         // Highlight everything not conform
  7657.                         overviewTable = $("#buildings_table");
  7658.                         tableHandler.init("buildings_table");
  7659.                
  7660.                         var menu = "<table class='vis' width='100%'>";
  7661.                         menu += "<tr><th>";
  7662.                         menu += "<input type=checkbox id=buildingOpti> " + trans.sp.buildOverview.optimistic + " ";
  7663.                         menu += "<input type=button id=buildingHighlight value='" + trans.sp.buildOverview.mark + "'>";
  7664.                         menu += "<input type=button id=buildingFilter value='" + trans.sp.buildOverview.filter + "'>";
  7665.                         menu += "</th></tr></table>";
  7666.                         overviewTable.before(menu);
  7667.                
  7668.                         function filterBuildings(cellAction, hideRows) {
  7669.                             var buildings = [];
  7670.                             overviewTable.find("tr:first img").each(function (i, v) {
  7671.                                 buildings[i] = this.src.substr(this.src.lastIndexOf('/') + 1);
  7672.                                 buildings[i] = buildings[i].substr(0, buildings[i].indexOf('.'));
  7673.                             });
  7674.                
  7675.                             var goners = $();
  7676.                             var opti = $("#buildingOpti").is(":checked");
  7677.                             overviewTable.find("tr:gt(0)").each(function () {
  7678.                                 var isOk = true;
  7679.                                 $(this).find("td:gt(3)").each(function (i, v) {
  7680.                                     var range = user_data.buildings[buildings[i]];
  7681.                                     if (range != undefined) {
  7682.                                         var text = parseInt($(this).text(), 10);
  7683.                                         if (text < range[0]) {
  7684.                                             $(this).css("background-color", user_data.colors.error);
  7685.                                             isOk = false;
  7686.                                         } else if (text > range[1] && !opti) {
  7687.                                             $(this).css("background-color", user_data.colors.good);
  7688.                                             isOk = false;
  7689.                                         } else
  7690.                                             $(this).css("background-color", "");
  7691.                                     }
  7692.                                 });
  7693.                                 if (hideRows && isOk) {
  7694.                                     goners = goners.add($(this));
  7695.                                     $("input:first", $(this)).val("");
  7696.                                 }
  7697.                             });
  7698.                             goners.remove();
  7699.                         }
  7700.                
  7701.                         $("#buildingHighlight").click(function () {
  7702.                             trackClickEvent("TableHighlight");
  7703.                             filterBuildings(function (cell, isOk) {
  7704.                                 cell.css("background-color", isOk ? "" : user_data.colors.neutral);
  7705.                             }, false);
  7706.                         });
  7707.                
  7708.                         $("#buildingFilter").click(function () {
  7709.                             trackClickEvent("TableRemove");
  7710.                             filterBuildings(function (cell, isOk) {
  7711.                                 cell.css("background-color", isOk ? "" : user_data.colors.neutral);
  7712.                             }, true);
  7713.                         });
  7714.                     } catch (e) { handleException(e, "overview-buildings"); }
  7715.                     //console.timeEnd("overview-buildings");
  7716.                 }());
  7717.             }
  7718.             // TECHS OVERVIEW // SMEDERIJ OVERVIEW // SMITHY OVERVIEW
  7719.             else if (location.href.indexOf('mode=tech') > -1) {
  7720.                 (function() {
  7721.                     //console.time("overview-techs");
  7722.                     try {
  7723.                         overviewTable = $("#techs_table");
  7724.                         tableHandler.init("techs_table");
  7725.                
  7726.                 // Highlight everything not conform usersettings
  7727.                         if (world_config.smithyLevels) {
  7728.                             var menu = "<table class='vis' width='100%'>";
  7729.                             menu += "<tr><th>";
  7730.                             menu += "<select id='groupType'>";
  7731.                             $.each(user_data.smithy, function (i, v) {
  7732.                                 menu += "<option value=" + i + ">" + v[0] + "</option>";
  7733.                             });
  7734.                             menu += "</select>";
  7735.                             menu += "<input type=checkbox id=buildingOpti> " + trans.sp.smithOverview.optimistic + " ";
  7736.                             menu += "<input type=button id=smithyHighlight value='" + trans.sp.smithOverview.mark + "'>";
  7737.                             menu += "<input type=button id=smithyFilter value='" + trans.sp.smithOverview.filter + "'>";
  7738.                             menu += "</th></tr></table>";
  7739.                             $("#techs_table").before(menu);
  7740.                
  7741.                             function filterTechs(cellAction, hideRows) {
  7742.                                 var goners = $();
  7743.                                 var opti = $("#buildingOpti").is(":checked");
  7744.                                 var def = user_data.smithy[$("#groupType").val()][1];
  7745.                                 $("#techs_table").find("tr:gt(0)").each(function () {
  7746.                                     var isOk = true;
  7747.                                     $(this).find("td:gt(2)").each(function (i, v) {
  7748.                                         var range = def[world_data.units[i]];
  7749.                                         if (i < world_data.units.length && range != undefined) {
  7750.                                             var text = parseInt($(this).text(), 10);
  7751.                                             if (text == '') {
  7752.                                                 text = 0;
  7753.                                             }
  7754.                                             if (text < range[0]) {
  7755.                                                 $(this).css("background-color", user_data.colors.error);
  7756.                                                 isOk = false;
  7757.                                             }
  7758.                                             else if (text > range[1] && !opti) {
  7759.                                                 $(this).css("background-color", user_data.colors.good);
  7760.                                                 isOk = false;
  7761.                                             } else {
  7762.                                                 $(this).css("background-color", "");
  7763.                                             }
  7764.                                         }
  7765.                                     });
  7766.                                     if (hideRows && isOk) {
  7767.                                         goners = goners.add($(this));
  7768.                                         $("input:first", $(this)).val("");
  7769.                                     }
  7770.                                 });
  7771.                                 goners.remove();
  7772.                             }
  7773.                
  7774.                             $("#smithyHighlight").click(function () {
  7775.                                 trackClickEvent("TableHighlight");
  7776.                                 filterTechs(function (cell, isOk) {
  7777.                                     cell.css("background-color", isOk ? "" : user_data.colors.neutral);
  7778.                                 }, false);
  7779.                             });
  7780.                
  7781.                             $("#smithyFilter").click(function () {
  7782.                                 trackClickEvent("TableRemove");
  7783.                                 filterTechs(function (cell, isOk) {
  7784.                                     cell.css("background-color", isOk ? "" : user_data.colors.neutral);
  7785.                                 }, true);
  7786.                             });
  7787.                         }
  7788.                     } catch (e) { handleException(e, "overview-techs"); }
  7789.                     //console.timeEnd("overview-techs");
  7790.                 }());
  7791.             }
  7792.             // GROUPS OVERVIEW
  7793.             else if (location.href.indexOf('mode=groups') > -1) {
  7794.                 (function() {
  7795.                     //console.time("overview-groups");
  7796.                     try {
  7797.                         overviewTable = $("#group_assign_table");
  7798.                         tableHandler.init("group_assign_table", {
  7799.                             hasBottomTotalRow: true
  7800.                         });
  7801.                
  7802.                         // TODO: edit groups: make div floatable and remember position
  7803.                         var menu = "";
  7804.                         menu += "<table class=vis width='100%'><tr><th>";
  7805.                
  7806.                         menu += trans.sp.defOverview.village + " <input type=text size=5 id=defFilterDistVillage value=''>";
  7807.                         menu += "<select id=defFilterDistType>";
  7808.                         menu += "<option value=1 selected>" + trans.sp.all.closer + "</option><option value=-1>" + trans.sp.all.further + "</option></select>";
  7809.                         menu += "&nbsp;F <input type=text size=3 id=defFilterDistFields value=" + user_data.restack.fieldsDistanceFilterDefault + ">";
  7810.                         menu += "<input type=button id=defFilterDist value='" + trans.sp.defOverview.distFilter + "' title='" + trans.sp.defOverview.distFilterTooltip + "'>";
  7811.                
  7812.                         menu += "&nbsp; | &nbsp;";
  7813.                         menu += "<input type=button id=attackFilter value='" + trans.sp.defOverview.filterUnderAttack + "'>";
  7814.                
  7815.                         menu += "<br>";
  7816.                
  7817.                         menu += "<input type=checkbox id=defReverseFilter title='" + trans.sp.commands.filtersReverse + "'> " + trans.sp.commands.filtersReverseInfo + ": ";
  7818.                
  7819.                         menu += "&nbsp; <input type=text size=12 id=defFilterTextValue value=''>";
  7820.                         menu += "<input type=button id=defFilterText value='" + trans.sp.groups.villageFilter + "'>";
  7821.                
  7822.                         menu += "&nbsp; <input type=textbox size=3 id=defFilterContinentText maxlength=2><input type=button id=defFilterContinent value='" + trans.sp.commands.continentFilter + "'>";
  7823.                
  7824.                         menu += "&nbsp; <input type=textbox size=3 id=defFilterAmountText maxlength=2><input type=button id=defFilterAmount value='" + trans.sp.groups.amountFilter + "'>";
  7825.                         menu += "&nbsp; <input type=textbox size=4 id=defFilterPointsText maxlength=5><input type=button id=defFilterPoints value='" + trans.sp.groups.pointsFilter + "'>";
  7826.                         menu += "&nbsp; <input type=textbox size=5 id=defFilterFarmText maxlength=6><input type=button id=defFilterFarm value='" + trans.tw.all.farm + "'>";
  7827.                
  7828.                         menu += "&nbsp; <input type=text size=12 id=defFilterGroupValue value=''>";
  7829.                         menu += "<input type=button id=defFilterGroup value='" + trans.sp.groups.groupNameFilter + "'>";
  7830.                         menu += "</th></tr></table>";
  7831.                
  7832.                         var selectAllRow = $("#group_assign_table tr:last");
  7833.                         $("#group_assign_table").before(menu).after("<table class=vis width='100%'><tr><th><input type=checkbox id=selectAllVisible> " + selectAllRow.text() + "</th></tr></table>");
  7834.                         selectAllRow.remove();
  7835.                
  7836.                         // Select all checkbox behavior
  7837.                         $("#selectAllVisible").change(function () {
  7838.                             var isChecked = $(this).is(":checked");
  7839.                             $("#group_assign_table input:checked").prop("checked", false);
  7840.                             if (isChecked) {
  7841.                                 $("#group_assign_table input[type='checkbox']").not(":hidden").prop("checked", true);
  7842.                             }
  7843.                
  7844.                             //$("#group_assign_table input:hidden").prop("checked", false);
  7845.                             //$("#group_assign_table input:visible").prop("checked", isChecked);
  7846.                         });
  7847.                
  7848.                         // Change tooltips when clicking the reverse filter checkbox
  7849.                         $("#defReverseFilter").change(function () {
  7850.                             var isChecked = $(this).is(":checked");
  7851.                             var defTrans = trans.sp.groups;
  7852.                             $("#defFilterText").attr("title", isChecked ? defTrans.villageFilterTitle : defTrans.villageFilterTitleRev);
  7853.                             $("#defFilterContinent").attr("title", isChecked ? trans.sp.commands.continentFilterTooltip : trans.sp.commands.continentFilterTooltipReverse);
  7854.                
  7855.                             $("#defFilterAmount").attr("title", isChecked ? defTrans.amountFilterTitle : defTrans.amountFilterTitleRev);
  7856.                             $("#defFilterPoints").attr("title", isChecked ? defTrans.pointsFilterTitle : defTrans.pointsFilterTitleRev);
  7857.                             $("#defFilterFarm").attr("title", isChecked ? defTrans.farmFilterTitle : defTrans.farmFilterTitleRev);
  7858.                
  7859.                             $("#defFilterGroup").attr("title", isChecked ? defTrans.groupNameFilterTitle : defTrans.groupNameFilterTitleRev);
  7860.                         });
  7861.                         $("#defReverseFilter").change();
  7862.                
  7863.                         /**
  7864.                          * Perform a filter on the groups table rows
  7865.                          * @param {function} filterStrategy jQuery object with the row is the first parameter
  7866.                          * @param {boolean} reverseFilter
  7867.                          * @param {function} [keepRowStrategy]
  7868.                          * @param {*} [tag] passed as second param to filterStrategy and keepRowStrategy
  7869.                          */
  7870.                
  7871.                         function filterGroupRows(filterStrategy, reverseFilter, keepRowStrategy, tag) {
  7872.                             if (typeof reverseFilter === "undefined") {
  7873.                                 reverseFilter = !$("#defReverseFilter").is(":checked");
  7874.                             }
  7875.                
  7876.                             var goners = $();
  7877.                             var totalVisible = 0;
  7878.                             $("#group_assign_table tr:gt(0)").each(function () {
  7879.                                 var row = $(this);
  7880.                                 if (row.is(":visible")) {
  7881.                                     if (!reverseFilter != !filterStrategy(row, tag)) {
  7882.                                         goners = goners.add(row);
  7883.                                         //$("input:eq(1)", row).val("");
  7884.                                     } else {
  7885.                                         totalVisible++;
  7886.                                         if (keepRowStrategy != null) {
  7887.                                             keepRowStrategy(row, tag);
  7888.                                         }
  7889.                                     }
  7890.                                 }
  7891.                             });
  7892.                             goners.remove();
  7893.                             var firstHeaderCell = $("#group_assign_table th:first");
  7894.                             var firstHeaderCellHtml = firstHeaderCell.html();
  7895.                             firstHeaderCell.html(firstHeaderCellHtml.substr(0, firstHeaderCellHtml.lastIndexOf(" ")) + " (" + totalVisible + ")");
  7896.                         }
  7897.                
  7898.                         // Filter on distance to given village
  7899.                         $("#defFilterDist").click(function () {
  7900.                             var targetVillage = getVillageFromCoords($("#defFilterDistVillage").val(), true);
  7901.                             if (!targetVillage.isValid) {
  7902.                                 alert(trans.sp.defOverview.distanceToVillageNoneEntered);
  7903.                                 return;
  7904.                             }
  7905.                
  7906.                             trackClickEvent("FilterDistance");
  7907.                             var reverseFilter = !($("#defFilterDistType").val() != "-1");
  7908.                             var maxDistance = parseInt($("#defFilterDistFields").val(), 10);
  7909.                
  7910.                             var isAlreadyVisible = $("#filterContext").size() == 1;
  7911.                             var distanceHeader =
  7912.                                 trans.sp.defOverview.distanceToVillage.replace(
  7913.                                     "{0}",
  7914.                                     "<a href='"
  7915.                                         + getUrlString("&screen=map&x=" + targetVillage.x + "&y=" + targetVillage.y + "'>")
  7916.                                         + targetVillage.coord + "</a>");
  7917.                
  7918.                             if (isAlreadyVisible) {
  7919.                                 $("#filterContext").html(distanceHeader);
  7920.                             } else {
  7921.                                 $("#group_assign_table").find("th:first").after("<th><span id=filterContext>" + distanceHeader + "</span> <img src='graphic/oben.png' class=sortDistance direction=up> <img src='graphic/unten.png' class=sortDistance direction=down></th>");
  7922.                                 $(".sortDistance").click(function () {
  7923.                                     if ($(this).attr("direction") == "up") {
  7924.                                         $("#group_assign_table").find("tr:gt(0)").filter(":visible").sortElements(function (a, b) {
  7925.                                             return parseInt($(a).attr("fieldAmount"), 10) > parseInt($(b).attr("fieldAmount"), 10) ? 1 : -1;
  7926.                                         });
  7927.                                     } else {
  7928.                                         $("#group_assign_table").find("tr:gt(0)").filter(":visible").sortElements(function (a, b) {
  7929.                                             return parseInt($(a).attr("fieldAmount"), 10) < parseInt($(b).attr("fieldAmount"), 10) ? 1 : -1;
  7930.                                         });
  7931.                                     }
  7932.                                 });
  7933.                             }
  7934.                
  7935.                             filterGroupRows(
  7936.                                 function (row, tag) {
  7937.                                     var compareVillage = getVillageFromCoords(row.find("td:first").text());
  7938.                                     tag.distance = getDistance(targetVillage.x, compareVillage.x, targetVillage.y, compareVillage.y, 'ram').fields;
  7939.                                     return tag.distance > maxDistance;
  7940.                
  7941.                                 }, reverseFilter,
  7942.                                 function (mainRow, tag) {
  7943.                                     mainRow.attr("fieldAmount", tag.distance);
  7944.                                     if (!isAlreadyVisible) {
  7945.                                         mainRow.find("td:first").after("<td><b>" + trans.sp.defOverview.fieldsPrefix.replace("{0}", parseInt(tag.distance, 10)) + "</b></td>");
  7946.                                     } else {
  7947.                                         mainRow.find("td:eq(1)").html("<b>" + trans.sp.defOverview.fieldsPrefix.replace("{0}", parseInt(tag.distance, 10)) + "</b>");
  7948.                                     }
  7949.                                 }, { distance: 0 });
  7950.                         });
  7951.                
  7952.                         // Filter on incoming attacks
  7953.                         $("#attackFilter").click(function () {
  7954.                             trackClickEvent("FilterUnderAttack");
  7955.                             filterGroupRows(function (row) {
  7956.                                 return $('td:first:not(:has(img[title=\'' + trans.tw.command.attack + '\']))', row).size() == 0;
  7957.                             });
  7958.                         });
  7959.                
  7960.                         // filter on village name
  7961.                         $("#defFilterText").click(function () {
  7962.                             trackClickEvent("FilterText");
  7963.                             var compareTo = $("#defFilterTextValue").val().toLowerCase();
  7964.                             if (compareTo.length > 0) {
  7965.                                 filterGroupRows(function (row) {
  7966.                                     return row.find("td:first").text().toLowerCase().indexOf(compareTo) != -1;
  7967.                                 });
  7968.                             }
  7969.                         });
  7970.                
  7971.                         // filter on group names
  7972.                         $("#defFilterGroup").click(function () {
  7973.                             trackClickEvent("FilterGroupName");
  7974.                             var compareTo = $("#defFilterGroupValue").val().toLowerCase();
  7975.                             if (compareTo.length > 0) {
  7976.                                 filterGroupRows(function (row) {
  7977.                                     return row.find("td:eq(4)").text().toLowerCase().indexOf(compareTo) != -1;
  7978.                                 });
  7979.                             }
  7980.                         });
  7981.                
  7982.                         $("#defFilterContinent").click(function () {
  7983.                             trackClickEvent("FilterContinent");
  7984.                             var compareTo = parseInt($("#defFilterContinentText").val(), 10);
  7985.                             if (compareTo >= 0) {
  7986.                                 compareTo = compareTo.toString();
  7987.                                 filterGroupRows(function (row) {
  7988.                                     var villageContinent = $.trim(row.find("td:first").text()),
  7989.                                         continentStart = villageContinent.lastIndexOf(trans.tw.all.continentPrefix);
  7990.                
  7991.                                     return villageContinent.substr(continentStart + 1) === compareTo;
  7992.                                 });
  7993.                             }
  7994.                         });
  7995.                
  7996.                         // filter on # groups
  7997.                         $("#defFilterAmount").click(function (){
  7998.                             trackClickEvent("FilterGroupCount");
  7999.                             var compareTo = parseInt($("#defFilterAmountText").val(), 10);
  8000.                             if (compareTo >= 0) {
  8001.                                 if (!$("#defReverseFilter").is(":checked")) {
  8002.                                     filterGroupRows(
  8003.                                         function (row) {
  8004.                                             return parseInt(row.find("td:eq(1)").text(), 10) > compareTo;
  8005.                                         },
  8006.                                         false);
  8007.                                 } else {
  8008.                                     filterGroupRows(
  8009.                                         function (row) {
  8010.                                             return parseInt(row.find("td:eq(1)").text(), 10) < compareTo;
  8011.                                         },
  8012.                                         false);
  8013.                                 }
  8014.                             }
  8015.                         });
  8016.                
  8017.                         $("#defFilterPoints").click(function () {
  8018.                             trackClickEvent("FilterPoints");
  8019.                             var compareTo = parseInt($("#defFilterPointsText").val(), 10);
  8020.                             if (compareTo >= 0) {
  8021.                                 filterGroupRows(function (row) {
  8022.                                     return parseInt(row.find("td:eq(2)").text().replace(".", ""), 10) < compareTo;
  8023.                                 });
  8024.                             }
  8025.                         });
  8026.                
  8027.                         $("#defFilterFarm").click(function () {
  8028.                             trackClickEvent("FilterFarm");
  8029.                             var compareTo = parseInt($("#defFilterFarmText").val(), 10);
  8030.                             if (compareTo >= 0) {
  8031.                                 filterGroupRows(function (row) {
  8032.                                     var farmValue = row.find("td:eq(3)").text();
  8033.                                     farmValue = parseInt(farmValue.substr(0, farmValue.indexOf("/")), 10);
  8034.                                     return farmValue < compareTo;
  8035.                                 });
  8036.                             }
  8037.                         });
  8038.                     } catch (e) { handleException(e, "overview-groups"); }
  8039.                     //console.timeEnd("overview-groups");
  8040.                 }());
  8041.             }
  8042.             // SUPPORT OVERVIEW
  8043.             else if (location.href.indexOf('type=support_detail') > -1
  8044.                 || location.href.indexOf('type=away_detail') > -1) {
  8045.  
  8046.                 (function() {
  8047.                     //console.time("overview-supportdetail");
  8048.                     try {
  8049.                         overviewTable = $("#units_table");
  8050.                         tableHandler.init("units_table", {
  8051.                             hasBottomTotalRow: true
  8052.                         });
  8053.                
  8054.                         /**
  8055.                          * true: we're on the support_detail page. false: away_detail page
  8056.                          * @type {boolean}
  8057.                          */
  8058.                         var isSupport = location.href.indexOf('type=support_detail') > -1;
  8059.                
  8060.                         /**
  8061.                          * Sets the correct rowcount in the first header cell
  8062.                          */
  8063.                         function setTotalCount() {
  8064.                             $("th:first", overviewTable).text(
  8065.                                 trans.sp.defOverview.totalVillages.replace(
  8066.                                     "{0}",
  8067.                                     $("tr.units_away", overviewTable).size()));
  8068.                         }
  8069.                        
  8070.                
  8071.                         var menu = "<table class='vis' width='100%'>";
  8072.                         menu += "<tr><th width='1%' nowrap>";
  8073.                         menu += "<input type=button id=defTotals value='" + trans.sp.defOverview.stackButton + "' title='" + trans.sp.defOverview.stackTooltip + "'>";
  8074.                         menu += "<input type=button id=defHideEmpty value='" + trans.sp.defOverview.filterNoSupport + "' title='" + trans.sp.defOverview.filterNoSupportTooltip + "'> ";
  8075.                        
  8076.                         menu += "</th><th width='96%' nowrap>";
  8077.                         menu += "<input type=button id=attackFilter value='" + trans.sp.defOverview.filterUnderAttack + "'>";
  8078.                         if (!isSupport) {
  8079.                             menu += "&nbsp; <input type=button id=defFilterBarbarian value='" + trans.sp.defOverview.barbarianFilter + "' title='" + trans.sp.defOverview.barbarianFilterTooltip + "'>";
  8080.                         } else {
  8081.                             menu += "</th><th width='1%' nowrap>";
  8082.                             menu += "<input type=text size=8 id=defFilterTotalPopValue value='" + user_data.restack.defaultPopulationFilterAmount + "'>";
  8083.                             menu += "<select id=defFilterTotalPopComparer>";
  8084.                             menu += "<option value=-1>" + trans.sp.all.less + "</option><option value=1 selected>" + trans.sp.all.more + "</option></select>";
  8085.                             menu += "<input type=button id=defFilterTotalPop value='" + trans.sp.defOverview.stackFilter + "' title='" + trans.sp.defOverview.stackFilterTooltip + "'>";
  8086.                        
  8087.                             menu += "</th><th width='1%' nowrap>";
  8088.                             menu += trans.sp.defOverview.village + " <input type=text size=5 id=defFilterDistVillage value=''>";
  8089.                             menu += "<select id=defFilterDistType>";
  8090.                             menu += "<option value=1 selected>" + trans.sp.all.closer + "</option><option value=-1>" + trans.sp.all.further + "</option></select>";
  8091.                             // TODO: untranslated F(ields)
  8092.                             menu += "&nbsp;F <input type=text size=3 id=defFilterDistanceValue value=" + user_data.restack.fieldsDistanceFilterDefault + ">";
  8093.                             menu += "<input type=button id=defFilterDist value='" + trans.sp.defOverview.distFilter + "' title='" + trans.sp.defOverview.distFilterTooltip + "'>";
  8094.                        
  8095.                             menu += "</th><th width='1%' nowrap>";
  8096.                             menu += " <input type=text size=8 id=defRestackTo value=" + user_data.restack.to + "> <input type=button id=defRestack value='" + trans.sp.defOverview.stackBBCodes + "' title='" + trans.sp.defOverview.stackBBCodesTooltip + "'>";
  8097.                             //menu += "</th></tr></table>";
  8098.                         }
  8099.                        
  8100.                         menu += "</th></tr></table>";
  8101.                         menu += "<table class='vis' width='100%'><tr><th width='1%' nowrap>";
  8102.                         menu += isSupport ? trans.sp.defOverview.extraFiltersSupport : trans.sp.defOverview.extraFiltersDefense;
  8103.                        
  8104.                         menu += "</th><th width='1%' nowrap>";
  8105.                         menu += "<input type=checkbox id=defReverseFilter title='" + trans.sp.defOverview.extraFiltersReverse + "'" + (user_data.restack.filterReverse ? " checked" : "") + "> " + trans.sp.defOverview.extraFiltersInfo;
  8106.                        
  8107.                         menu += "</th><th width='1%' nowrap>";
  8108.                         menu += "<input type=text size=3 id=defFilterDistanceValue value=" + user_data.restack.fieldsDistanceFilterDefault + "> <input type=button id=defFilterDistance value='" + trans.sp.defOverview.distFilter2 + "'>";
  8109.                        
  8110.                         menu += "</th><th width='1%' nowrap>";
  8111.                         menu += "&nbsp; <span style='background-color: #ecd19a; border: 1px solid black' id='unitFilterBox'>";
  8112.                         menu += "&nbsp; <img src='graphic/unit/unit_snob.png' id=filtersnob>&nbsp; <img src='graphic/unit/unit_spy.png' id=filterspy>";
  8113.                         menu += "&nbsp; <img src='graphic/buildings/barracks.png' id=filterAttack>&nbsp;<img src='graphic/unit/def.png' id=filterDefense>&nbsp;<img id=filterSupport src='graphic/command/support.png'>&nbsp;";
  8114.                         menu += "</span>&nbsp;&nbsp;";
  8115.                        
  8116.                         menu += "</th><th width='96%' nowrap>";
  8117.                         menu += "<input type=text size=12 id=defFilterTextValue value=''>";
  8118.                         menu += "<input type=button id=defFilterText value='" + trans.sp.defOverview.freeTextFilter + "'>";
  8119.                         menu += "</th></tr></table>";
  8120.                        
  8121.                         overviewTable.before(menu);
  8122.                        
  8123.                         $("#defReverseFilter").change(function () {
  8124.                             var isChecked = $(this).is(":checked");
  8125.                             var defTrans = trans.sp.defOverview;
  8126.                             $("#unitFilterBox").find("img:eq(0)").attr("title", isChecked ? defTrans.nobleFilter : defTrans.nobleFilterRev);
  8127.                             $("#unitFilterBox").find("img:eq(1)").attr("title", isChecked ? defTrans.spyFilter : defTrans.spyFilterRev);
  8128.                             $("#unitFilterBox").find("img:eq(2)").attr("title", isChecked ? defTrans.attackFilter : defTrans.attackFilterRev);
  8129.                             $("#unitFilterBox").find("img:eq(3)").attr("title", isChecked ? defTrans.supportFilter : defTrans.supportFilterRev);
  8130.                        
  8131.                             $("#unitFilterBox").find("img:eq(4)").attr("title", (isSupport ? defTrans.otherPlayerFilterFrom : defTrans.otherPlayerFilterTo).replace("{action}", isChecked ? defTrans.otherPlayerFilterShow : defTrans.otherPlayerFilterHide));
  8132.                             $("#defFilterText").attr("title", defTrans.freeTextFilterTooltip.replace("{villageType}", isSupport ? defTrans.filterTooltipVillageTypeSupporting : defTrans.filterTooltipVillageTypeSupported).replace("{filterType}", isChecked ? defTrans.freeTextFilterTooltipFilterTypeWith : defTrans.freeTextFilterTooltipFilterTypeWithout));
  8133.                             $("#defFilterDistance").attr("title", defTrans.distanceFilterTooltip.replace("{villageType}", isSupport ? defTrans.filterTooltipVillageTypeSupporting : defTrans.filterTooltipVillageTypeSupported).replace("{filterType}", !isChecked ? defTrans.distanceFilterTooltipFilterTypeCloser : defTrans.distanceFilterTooltipFilterTypeFurther));
  8134.                         });
  8135.                         $("#defReverseFilter").change();
  8136.                         // This file contains the filters on the FIRST row of the menu
  8137.                        
  8138.                         // UNDER ATTACK FILTER
  8139.                         $("#attackFilter").click(function () {
  8140.                             trackClickEvent("FilterAttack");
  8141.                             var reverseFilter = true; // never reverse this filter!
  8142.                        
  8143.                             var filterStrategy =
  8144.                                 function (row) {
  8145.                                     return $('td:first:not(:has(img[title=\'' + trans.tw.command.attack + '\']))', row).size() == 0;
  8146.                                 };
  8147.                        
  8148.                             var lastRow = $("tr:last", overviewTable).get(0);
  8149.                             var goners = $();
  8150.                             $("tr.units_away", overviewTable).each(function () {
  8151.                                 var self = $(this);
  8152.                                 if (!reverseFilter != !filterStrategy(self)) {
  8153.                                     goners = goners.add(self);
  8154.                        
  8155.                                     var nextRow = self.next();
  8156.                                     while (!nextRow.hasClass("units_away") && nextRow.get(0) !== lastRow) {
  8157.                                         goners = goners.add(nextRow);
  8158.                                         nextRow = nextRow.next();
  8159.                                     }
  8160.                                 }
  8161.                             });
  8162.                             goners.remove();
  8163.                             setTotalCount();
  8164.                        
  8165.                             // this is already done in the code above :)
  8166.                             //$("#defHideEmpty").click();
  8167.                        
  8168.                             if (user_data.restack.calculateDefTotalsAfterFilter
  8169.                                 && !$("#defTotals").is(":disabled")) {
  8170.                        
  8171.                                 $("#defTotals").click();
  8172.                             }
  8173.                         });
  8174.                        
  8175.                        
  8176.                        
  8177.                         /**
  8178.                          * Performs a filter on the OWN villages (ie not the villages supporting the OWN villages)
  8179.                          * The 'row' parameter are the rows with class grandTotal. These are the rows that get added after (below) all
  8180.                          * supporting os rows when the total def is being calculated.
  8181.                          * @param {function} filterStrategy return a boolean to filter all rows for the OWN away. first parameter is the (jQuery) row with the OWN village
  8182.                          * @param {boolean} reverseFilter reverse the above strategy
  8183.                          * @param {function} [survivorRowStrategy] execute on all rows that are not being removed (gets passed the row and optional tag)
  8184.                          * @param {*} [tag] this value is passed onto filterStrategy and survivorRowStrategy as the second parameter (row is the first param)
  8185.                          */
  8186.                         function filterMainRows(filterStrategy, reverseFilter, survivorRowStrategy, tag) {
  8187.                             if (!$("#defTotals").is(":disabled")) {
  8188.                                 $("#defTotals").click();
  8189.                             }
  8190.                        
  8191.                             /*var goners = $();
  8192.                             $("tr.grandTotal", overviewTable).each(function () {
  8193.                                 var self = $(this),
  8194.                                     prev;
  8195.                                 if (!reverseFilter != !filterStrategy(self, tag)) {
  8196.                                     goners = goners.add(self).add(self.next());
  8197.                        
  8198.                                     prev = self.prev();
  8199.                                     while (!prev.hasClass("units_away")) {
  8200.                                         goners = goners.add(prev);
  8201.                                         prev = prev.prev();
  8202.                                     }
  8203.                        
  8204.                                     goners = goners.add(prev);
  8205.                                 } else if (survivorRowStrategy != null) {
  8206.                                     prev = self.prev();
  8207.                                     while (!prev.hasClass("units_away")) {
  8208.                                         prev = prev.prev();
  8209.                                     }
  8210.                                     survivorRowStrategy(prev, tag);
  8211.                                 }
  8212.                             });
  8213.                              goners.remove();
  8214.                              setTotalCount();*/
  8215.                        
  8216.                             var lastRow = $("tr:last", overviewTable).get(0);
  8217.                             var goners = $();
  8218.                             $("tr.units_away", overviewTable).each(function () {
  8219.                                 var self = $(this);
  8220.                                 if (!reverseFilter != !filterStrategy(self, tag)) {
  8221.                                     goners = goners.add(self);
  8222.                        
  8223.                                     var nextRow = self.next();
  8224.                                     while (!nextRow.hasClass("units_away") && nextRow.get(0) !== lastRow) {
  8225.                                         goners = goners.add(nextRow);
  8226.                                         nextRow = nextRow.next();
  8227.                                     }
  8228.                                 }
  8229.                                 else if (survivorRowStrategy != null) {
  8230.                                     /*prev = self.prev();
  8231.                                     while (!prev.hasClass("units_away")) {
  8232.                                         prev = prev.prev();
  8233.                                     }*/
  8234.                                     survivorRowStrategy(self, tag);
  8235.                                 }
  8236.                             });
  8237.                             goners.remove();
  8238.                             setTotalCount();
  8239.                         }
  8240.                        
  8241.                        
  8242.                        
  8243.                         // filter the OWN villages on less/more population (requires total calculation)
  8244.                         $("#defFilterTotalPop").click(function () {
  8245.                             trackClickEvent("FilterFarm");
  8246.                             var reverseFilter = $("#defFilterTotalPopComparer").val() != "-1";
  8247.                             var compareTo = parseInt($("#defFilterTotalPopValue").val(), 10);
  8248.                        
  8249.                             filterMainRows(
  8250.                                 //q(row.attr("village") + "is:" + row.attr("population"));
  8251.                                 function (row) { return (parseInt(row.attr("population"), 10) > compareTo); },
  8252.                                 reverseFilter);
  8253.                         });
  8254.                        
  8255.                         // filter the OWN villages on less/more distance to given coordinates (requires total calculation)
  8256.                         $("#defFilterDist").click(function () {
  8257.                             var targetVillage = getVillageFromCoords($("#defFilterDistVillage").val(), true);
  8258.                             if (!targetVillage.isValid) {
  8259.                                 alert(trans.sp.defOverview.distanceToVillageNoneEntered);
  8260.                                 return;
  8261.                             }
  8262.                        
  8263.                             trackClickEvent("FilterDistanceToX");
  8264.                             var reverseFilter = !($("#defFilterDistType").val() != "-1");
  8265.                             var maxDistance = parseInt($("#defFilterDistanceValue").val(), 10);
  8266.                        
  8267.                             // Change text of th cell to 'distance to ' + given village
  8268.                             overviewTable.find("th:eq(1)").html(
  8269.                                 trans.sp.defOverview.distanceToVillage.replace(
  8270.                                     "{0}",
  8271.                                     "<a href='"
  8272.                                         + getUrlString("&screen=map&x=" + targetVillage.x + "&y=" + targetVillage.y + "'>")
  8273.                                         + targetVillage.coord + "</a>"));
  8274.                        
  8275.                             filterMainRows(
  8276.                                 function (row, tag) {
  8277.                                     var compareVillage = getVillageFromCoords(row.attr("village"));
  8278.                                     tag.distance = getDistance(targetVillage.x, compareVillage.x, targetVillage.y, compareVillage.y, 'ram').fields;
  8279.                                     return tag.distance > maxDistance;
  8280.                                 },
  8281.                                 reverseFilter,
  8282.                                 function (mainRow, tag) {
  8283.                                     // Adds the distance between OWN village and the user given coordinates
  8284.                                     mainRow.find("td:eq(1)").html("<b>" + trans.sp.defOverview.fieldsPrefix.replace("{0}", parseInt(tag.distance, 10)) + "</b>");
  8285.                                 },
  8286.                                 { distance: 0 });
  8287.                         });
  8288.                         // Calculate villages that don't have x population defense
  8289.                         // and create BBcodes textarea for it
  8290.                         $("#defRestack").click(function () {
  8291.                             trackClickEvent("BBCodeOutput");
  8292.                             if (!$("#defTotals").attr("disabled")) {
  8293.                                 $("#defTotals").click();
  8294.                             }
  8295.                        
  8296.                             var restackTo = parseInt($("#defRestackTo").val(), 10);
  8297.                             var counter = 0;
  8298.                        
  8299.                             var request = "";
  8300.                             $("tr.grandTotal", overviewTable).each(function () {
  8301.                                 var self = $(this);
  8302.                                 var total = parseInt(self.attr('population'), 10);
  8303.                                 if (restackTo - total > user_data.restack.requiredDifference) {
  8304.                                     var villageCoords = self.attr("village");
  8305.                                     counter++;
  8306.                                     request += counter + "[village]" + villageCoords + "[/village] (" + parseInt((restackTo - total) / 1000, 10) + trans.sp.defOverview.thousandSuffix + ")\n";
  8307.                                 }
  8308.                             });
  8309.                        
  8310.                             if ($("#textsArea").size() == 0) {
  8311.                                 $(this).parent().parent().parent().parent().after(
  8312.                                     "<table class='vis' width='100%'><tr>"
  8313.                                         + "<td id=textsArea width='50%' valign='top'></td>"
  8314.                                         + "<td id='extraTextsArea' width='50%' valign='top'>"
  8315.                                         + trans.sp.defOverview.freeText
  8316.                                         + "<br><textarea cols=50 rows=9></textarea></td>"
  8317.                                         + "</tr></table>");
  8318.                        
  8319.                                 $("#textsArea").parent().after("<tr><td colspan='2'><input type=button value='" + trans.sp.all.close + "' id=closeTextsArea></td></tr>");
  8320.                             } else {
  8321.                                 $("#textsArea").html("");
  8322.                             }
  8323.                        
  8324.                             var title = trans.sp.troopOverview.restackTitle
  8325.                                 .replace("{to}", parseInt(restackTo / 1000, 10))
  8326.                                 .replace("{requiredDiff}", parseInt(user_data.restack.requiredDifference / 1000, 10));
  8327.                        
  8328.                             $("#textsArea").append(title + "<br><textarea cols=50 rows=10 id=restackArea>" + request + "</textarea>");
  8329.                        
  8330.                             $("#closeTextsArea").click(function() {
  8331.                                 $("#textsArea").parent().parent().remove();
  8332.                             });
  8333.                         });
  8334.                         // Check all villages checkbox replacement
  8335.                         $("input.selectAll").replaceWith("<input type=checkbox id=selectAllVisible>");
  8336.                         $("#selectAllVisible").change(function () {
  8337.                             var isChecked = $(this).is(":checked");
  8338.                        
  8339.                             $("input.village_checkbox:hidden", overviewTable).prop("checked", false);
  8340.                             $("input.village_checkbox:visible", overviewTable).prop("checked", isChecked);
  8341.                         });
  8342.                        
  8343.                        
  8344.                         // Hide all OWN rows/villages that don't have any support rows anymore
  8345.                         $("#defHideEmpty").click(function () {
  8346.                             trackClickEvent("FilterEmpty");
  8347.                             var goners = $();
  8348.                             if ($("#defTotals").is(":disabled")) {
  8349.                                 $("tr.units_away", overviewTable).each(function () {
  8350.                                     var mainRow = $(this),
  8351.                                         nextRow = mainRow.next();
  8352.                        
  8353.                                     if (nextRow.hasClass("grandTotal")) {
  8354.                                         goners = goners.add(mainRow).add(nextRow.next()).add(nextRow);
  8355.                                     }
  8356.                                     else if (nextRow.hasClass("units_away")) {
  8357.                                         goners = goners.add(mainRow);
  8358.                                     }
  8359.                                 });
  8360.                             } else {
  8361.                                 $("tr.units_away", overviewTable).each(function () {
  8362.                                     var mainRow = $(this),
  8363.                                         nextRow = mainRow.next();
  8364.                        
  8365.                                     if (nextRow.hasClass("units_away")) {
  8366.                                         goners = goners.add(mainRow);
  8367.                                     }
  8368.                                 });
  8369.                             }
  8370.                        
  8371.                             goners.remove();
  8372.                             setTotalCount();
  8373.                         });
  8374.                        
  8375.                        
  8376.                         // Calculate the amount of def in each village
  8377.                         // Give sensible background row colors
  8378.                         // Add attributes to the grandTotal class rows 'village' (coords) and 'population'
  8379.                         // Add attribute 'distance' the distance between OWN and supporting villages, in fields
  8380.                         $("#defTotals").click(function () {
  8381.                             trackClickEvent("FilterTotalDef");
  8382.                             $(this).attr("disabled", true);
  8383.                             var rowColor = 0;
  8384.                             var goners = $();
  8385.                             overviewTable.find("tr.units_away").each(function () {
  8386.                                 var self = $(this);
  8387.                                 var firstCell = self.find("td:first");
  8388.                                 var villageCoord = getVillageFromCoords(firstCell.text().replace(firstCell.find(".quickedit-label").attr("data-text"), ""));
  8389.                        
  8390.                                 // ensure row color swapping for each own village
  8391.                                 rowColor++;
  8392.                                 if (rowColor % 2 == 1) {
  8393.                                     self.removeClass("row_a").addClass("row_b");
  8394.                                 } else {
  8395.                                     self.removeClass("row_b").addClass("row_a");
  8396.                                 }
  8397.                        
  8398.                                 // village attribute both to .units_away and .grandTotal rows ;)
  8399.                                 self.attr("village", villageCoord.coord);
  8400.                        
  8401.                                 var nextRow = self.next();
  8402.                                 if (nextRow.hasClass("units_away")) {
  8403.                                     if (user_data.restack.removeRowsWithoutSupport) {
  8404.                                         goners = goners.add(self);
  8405.                                     }
  8406.                                 } else {
  8407.                                     // calculate total support
  8408.                                     var grandTotal = 0;
  8409.                                     var totals = [];
  8410.                                     while (nextRow.hasClass("row_a") || nextRow.hasClass("row_b")) {
  8411.                                         // supporting rows loop
  8412.                                         var total = 0;
  8413.                                         $("td:gt(0)", nextRow).each(function (i) {
  8414.                                             // total support per supporting village
  8415.                                             var cellSelf = $(this);
  8416.                                             var cellContent = $.trim(cellSelf.text());
  8417.                                             if (!(cellContent == '0' || i >= world_data.unitsPositionSize.length)) {
  8418.                                                 total += cellContent * world_data.unitsPositionSize[i];
  8419.                                                 if (totals[i] == undefined) {
  8420.                                                     totals[i] = parseInt(cellContent, 10);
  8421.                                                 } else {
  8422.                                                     totals[i] += parseInt(cellContent, 10);
  8423.                                                 }
  8424.                                             }
  8425.                                         });
  8426.                                         grandTotal += total;
  8427.                                         $("td:eq(" + (world_data.unitsPositionSize.length + 1) + ")", nextRow).text(formatNumber(total));
  8428.                        
  8429.                                         // print distance between own village
  8430.                                         var supportedCell = $("td:first", nextRow);
  8431.                                         var supportedVillage = getVillageFromCoords(supportedCell.text());
  8432.                                         var distance = parseInt(getDistance(supportedVillage.x, villageCoord.x, supportedVillage.y, villageCoord.y, 'ram').fields, 10);
  8433.                                         //supportedCell.html(supportedCell.html() + ' <b>' + trans.sp.all.fieldsSuffix.replace("{0}", distance) + '</b>');
  8434.                                         supportedCell.append(' <b>' + trans.sp.all.fieldsSuffix.replace("{0}", distance) + '</b>');
  8435.                                         nextRow.attr("distance", distance);
  8436.                        
  8437.                                         if (rowColor % 2 == 1) {
  8438.                                             nextRow.removeClass("row_a").addClass("row_b");
  8439.                                         } else {
  8440.                                             nextRow.removeClass("row_b").addClass("row_a");
  8441.                                         }
  8442.                        
  8443.                                         nextRow = nextRow.next();
  8444.                                     }
  8445.                        
  8446.                                     // row colors for the support villages
  8447.                                     if (rowColor % 2 == 1) {
  8448.                                         nextRow.removeClass("row_a").addClass("row_b");
  8449.                                     } else {
  8450.                                         nextRow.removeClass("row_b").addClass("row_a");
  8451.                                     }
  8452.                        
  8453.                                     var troopCells = "";
  8454.                                     for (var i = 0; i < world_data.unitsPositionSize.length; i++) {
  8455.                                         if (typeof totals[i] !== 'undefined') {
  8456.                                             troopCells += "<td>" + formatNumber(totals[i]) + "</td>";
  8457.                                         } else {
  8458.                                             troopCells += "<td><span class=hidden>0</span></td>";
  8459.                                         }
  8460.                                     }
  8461.                        
  8462.                                     self.attr("population", grandTotal);
  8463.                                     var color = getStackColor(grandTotal);
  8464.                                     color = "<td style='background-color: " + color + "; border:1px solid black'>" + formatNumber(grandTotal) + "</td>";
  8465.                        
  8466.                                     nextRow.before("<tr class='grandTotal " + (rowColor % 2 == 1 ? "row_b" : "row_a") + "' village='" + villageCoord.coord + "' population='" + grandTotal + "'><td>&nbsp;</td><td>" + (isSupport ? trans.sp.defOverview.totalFromOtherVillages : trans.sp.defOverview.totalInOtherVillages) + "</td>" + troopCells + color + "</tr><tr height=10></tr>");
  8467.                                 }
  8468.                             });
  8469.                        
  8470.                             goners.remove();
  8471.                         });
  8472.                         // This file contains the filters on the SECOND row of the menu
  8473.                        
  8474.                         /**
  8475.                          * Loops over all supporting rows and removes the rows that fail the filterStrategy predicate. Where the
  8476.                          * filterMainRows function loops over the OWN villages, this one loops over all the others.
  8477.                          * @param {Function} filterStrategy gets each jQuery row that represents a supporting village passed as parameter
  8478.                          * @param {string=after} [calcDefTotalsTime] is "before" or "after". before if it uses row attributes set by #defTotals
  8479.                          */
  8480.                         function filterTable(filterStrategy, calcDefTotalsTime) {
  8481.                             if (typeof calcDefTotalsTime === 'undefined') {
  8482.                                 calcDefTotalsTime = "after";
  8483.                             }
  8484.                             if (calcDefTotalsTime === "before" && !$("#defTotals").is(":disabled")) {
  8485.                                 $("#defTotals").click();
  8486.                             }
  8487.                             var totalDefCalced = $("#defTotals").is(":disabled");
  8488.                        
  8489.                             var reverseFilter = $("#defReverseFilter").is(":checked");
  8490.                             var goners = $();
  8491.                             $("tr", overviewTable).slice(1).each(function () {
  8492.                                 var self = $(this);
  8493.                                 if ((totalDefCalced && self.attr("distance"))
  8494.                                     || (!totalDefCalced && (self.hasClass("row_a") || self.hasClass("row_b")))) {
  8495.                        
  8496.                                     if (!reverseFilter != !filterStrategy(self)) {
  8497.                                         goners = goners.add(self);
  8498.                                     }
  8499.                                 }
  8500.                             });
  8501.                             goners.remove();
  8502.                             setTotalCount();
  8503.                             if (user_data.restack.autohideWithoutSupportAfterFilter) {
  8504.                                 $("#defHideEmpty").click();
  8505.                             }
  8506.                        
  8507.                             if (user_data.restack.calculateDefTotalsAfterFilter
  8508.                                 && calcDefTotalsTime === "after"
  8509.                                 && !$("#defTotals").is(":disabled")) {
  8510.                        
  8511.                                 $("#defTotals").click();
  8512.                             }
  8513.                         }
  8514.                        
  8515.                        
  8516.                        
  8517.                        
  8518.                        
  8519.                         // NOBLES and SCOUTS
  8520.                         $("#filtersnob, #filterspy").click( function () {
  8521.                             trackClickEvent("FilterSnobOrSpy");
  8522.                             var position = $.inArray($(this).attr("id").substr(6), world_data.units) + 1;
  8523.                             filterTable(function (row) {
  8524.                                 return row.find("td").eq(position).text() != "0";
  8525.                             });
  8526.                         });
  8527.                        
  8528.                         // OTHER PLAYERS SUPPORT
  8529.                         $("#filterSupport").click( function () {
  8530.                             trackClickEvent("FilterSupport");
  8531.                             filterTable(function (row) {
  8532.                                 return row.find("td:first a").length != 2;
  8533.                             });
  8534.                         });
  8535.                        
  8536.                         // DEFENSIVE & OFFENSIVE UNITS
  8537.                         $("#filterAttack, #filterDefense").click( function () {
  8538.                             trackClickEvent("FilterOffOrDef");
  8539.                             var unitArray = $(this).attr('id') == "filterDefense" ? world_data.units_def : world_data.units_off;
  8540.                             filterTable(function (row) {
  8541.                                 var hideRow = false;
  8542.                                 $("td:gt(0)", row).each(function (i) {
  8543.                                     if (world_data.units[i] != undefined
  8544.                                         && world_data.units[i] != "heavy"
  8545.                                         && parseInt($(this).text(), 10) > 0
  8546.                                         && $.inArray(world_data.units[i], unitArray) > -1) {
  8547.                        
  8548.                                         hideRow = true;
  8549.                                         return false;
  8550.                                     }
  8551.                                 });
  8552.                        
  8553.                                 return hideRow;
  8554.                             });
  8555.                         });
  8556.                        
  8557.                         /// BARBARIAN VILLAGES filter
  8558.                         $("#defFilterBarbarian").click( function () {
  8559.                             trackClickEvent("FilterBarbarian");
  8560.                             filterTable(function (row) {
  8561.                                 var text = row.find("td:first").text();
  8562.                                 return text.match(/\(---\)\s+\(F\d+\)$/); // Unlocalized F(ields) string
  8563.                             });
  8564.                         });
  8565.                        
  8566.                         // TEXT FILTER
  8567.                         $("#defFilterText").click( function () {
  8568.                             trackClickEvent("FilterText");
  8569.                             var compareTo = $("#defFilterTextValue").val().toLowerCase();
  8570.                             if (compareTo.length > 0)
  8571.                                 filterTable(function (row) {
  8572.                                     return row.text().toLowerCase().indexOf(compareTo) == -1;
  8573.                                 });
  8574.                         });
  8575.                        
  8576.                         // DISTANCE between OWN and supporting village
  8577.                         $("#defFilterDistance").click(function () {
  8578.                             trackClickEvent("FilterDistance");
  8579.                             var maxDistance = $("#defFilterDistanceValue").val();
  8580.                             filterTable(
  8581.                                 function (row) {
  8582.                                     var distance = $(row).attr("distance");
  8583.                                     return (distance != '' && parseInt(distance, 10) < maxDistance);
  8584.                                 },
  8585.                                 "before");
  8586.                         });
  8587.                
  8588.                     } catch (e) { handleException(e, "overview-supportdetail"); }
  8589.                     //console.timeEnd("overview-supportdetail");
  8590.                 }());
  8591.             }
  8592.             // COMMANDS OVERVIEW
  8593.             else if (location.href.indexOf('mode=commands') > -1) {
  8594.                 (function() {
  8595.                     //console.time("overview-commands");
  8596.                     try {
  8597.                         overviewTable = $("#commands_table");
  8598.                         tableHandler.init("commands_table", {
  8599.                             hasBottomTotalRow: true
  8600.                         });
  8601.                
  8602.                         var commandListType = getQueryStringParam("type");
  8603.                
  8604.                         var menu = "";
  8605.                         menu += "<table class=vis width='100%'>";
  8606.                         menu += "<tr>";
  8607.                         if (location.href.indexOf('type=all') > -1 || location.href.indexOf('&type=') == -1) {
  8608.                             menu += "<th width='1%'>";
  8609.                             menu += "<input type=button id=filterReturning value='" + trans.sp.commands.filterReturn + "' title=\"" + trans.sp.commands.filterReturnTooltip + "\">";
  8610.                             menu += "</th>";
  8611.                         }
  8612.                
  8613.                         menu += "<th width='1%' nowrap>";
  8614.                         menu += "<input type=checkbox id=sortSum " + (user_data.command.sumRow ? "checked" : "") + "> " + trans.sp.commands.totalRows + " ";
  8615.                         var isSupport = location.href.indexOf('type=support') > -1;
  8616.                         menu += "<input type=button id=sortIt value='" + trans.sp.commands.group + "'>";
  8617.                
  8618.                         menu += "</th><th width='98%'>";
  8619.                
  8620.                         menu += "<input type=button id=BBCodeOutput value='" + trans.sp.commands.bbCodeExport + "' title='" + trans.sp.commands.bbCodeExportTooltip + "'>";
  8621.                
  8622.                         if (commandListType !== "attack" && commandListType !== "return") {
  8623.                             menu += "&nbsp; &nbsp;";
  8624.                             menu += "<input type=text id=supportPlayerName size=>&nbsp;";
  8625.                             menu += "<input type=button id=supportPlayerExport value='" + trans.sp.commands.supportPlayerExport + "' title='" + trans.sp.commands.supportPlayerExportTooltip + "'>";
  8626.                         }
  8627.                
  8628.                         menu += "</th></tr></table>";
  8629.                
  8630.                         // second row
  8631.                         menu += "<table><tr><th width='1%' nowrap>";
  8632.                         menu += "<input type=checkbox id=defReverseFilter title='" + trans.sp.commands.filtersReverse + "'> " + trans.sp.commands.filtersReverseInfo + ": ";
  8633.                
  8634.                         menu += "</th><th width='1%' nowrap>";
  8635.                         menu += "<span style='background-color: #ecd19a; border: 1px solid black' id='unitFilterBox'>";
  8636.                         menu += "&nbsp; <img src='graphic/unit/unit_snob.png' id=filtersnob>&nbsp; <img src='graphic/unit/unit_spy.png' id=filterspy>&nbsp; <img src='graphic/face.png' id=filterFake>&nbsp;";
  8637.                         menu += "&nbsp; </span>";
  8638.                
  8639.                         menu += "</th><th width='1%' nowrap>";
  8640.                         menu += "<input type=text size=12 id=defFilterTextValue value=''>";
  8641.                         menu += "<input type=button id=defFilterText value='" + trans.sp.commands.freeTextFilter + "'>";
  8642.                
  8643.                         menu += "</th><th width='97%' nowrap>";
  8644.                         menu += "<input type=textbox size=3 id=defFilterContinentText maxlength=2><input type=button id=defFilterContinent value='" + trans.sp.commands.continentFilter + "'>";
  8645.                
  8646.                         menu += "</th></tr>";
  8647.                         menu += "</table>";
  8648.                         $("#commands_table").before(menu);
  8649.                
  8650.                         $("#select_all").replaceWith("<input type='checkbox' id='selectAll'>");
  8651.                         var selectAllCheckboxes = function() {
  8652.                             var isChecked = $("#selectAll").is(":checked");
  8653.                             $("#commands_table tr:visible").find(":checkbox").prop("checked", isChecked);
  8654.                         };
  8655.                         $("#selectAll").change(selectAllCheckboxes);
  8656.                
  8657.                         var offsetToUnits = 3;
  8658.                
  8659.                         $("#defReverseFilter").change( function () {
  8660.                             var isChecked = $(this).is(":checked");
  8661.                             var defTrans = trans.sp.commands;
  8662.                             $("#unitFilterBox").find("img:eq(0)").attr("title", !isChecked ? defTrans.nobleFilter : defTrans.nobleFilterRev);
  8663.                             $("#unitFilterBox").find("img:eq(1)").attr("title", isChecked ? defTrans.spyFilter : defTrans.spyFilterRev);
  8664.                             $("#unitFilterBox").find("img:eq(2)").attr("title", !isChecked ? defTrans.fakeFilter : defTrans.fakeFilterRev);
  8665.                
  8666.                             $("#defFilterContinent").attr("title", isChecked ? defTrans.continentFilterTooltip : defTrans.continentFilterTooltipReverse);
  8667.                
  8668.                             $("#defFilterText").attr("title", defTrans.freeTextFilterTooltip.replace("{filterType}", isChecked ? defTrans.freeTextFilterTooltipFilterTypeWith : defTrans.freeTextFilterTooltipFilterTypeWithout));
  8669.                         });
  8670.                
  8671.                         $("#defReverseFilter").change();
  8672.                         var hasGrouped = false;
  8673.                
  8674.                         // generate bb code or JSON (for player os) export
  8675.                         $("#BBCodeOutput,#supportPlayerExport").click(function () {
  8676.                             trackClickEvent($(this).attr("id"));
  8677.                             var villages = [];
  8678.                             var request = {};
  8679.                             var filter = hasGrouped ? "tr.command" : "tr:gt(0)";
  8680.                             $("#commands_table " + filter).filter(":visible").each(function () {
  8681.                                 var row = $(this);
  8682.                                 var cells = $("td", row);
  8683.                                 var firstCell = cells.first();
  8684.                                 var commandType = firstCell.find("img:first").attr("src");
  8685.                
  8686.                                 if (typeof commandType !== 'undefined'
  8687.                                     && commandType.indexOf("command/cancel.png") == -1
  8688.                                     && commandType.indexOf("command/other_back.png") == -1
  8689.                                     && commandType.indexOf("command/back.png") == -1
  8690.                                     && commandType.indexOf("command/return.png") == -1) {
  8691.                
  8692.                                     // We get the village coords from the description of the command
  8693.                                     // Meaning if the user changes the name to "blabla" that we can't parse it
  8694.                                     var village = getVillageFromCoords($.trim(firstCell.text()));
  8695.                                     //assert(village.isValid, $.trim(firstCell.text()) + " could not be converted to village");
  8696.                                     if (village.isValid) {
  8697.                                         if (request[village.coord] == undefined) {
  8698.                                             request[village.coord] = { village: village.coord, attacks: [], hasSupport: false };
  8699.                                             villages.push(village.coord);
  8700.                                         }
  8701.                
  8702.                                         var unitsSent = {};
  8703.                                         $.each(world_data.units,
  8704.                                             function (i, val) {
  8705.                                                 unitsSent[val] = parseInt(cells.eq(offsetToUnits + i).text(), 10);
  8706.                                             });
  8707.                
  8708.                                         var isSupport = false;
  8709.                                         if (commandListType == "support") {
  8710.                                             isSupport = true;
  8711.                                         }
  8712.                                         else if (commandListType == "attack") {
  8713.                                             isSupport = false;
  8714.                                         } else {
  8715.                                             isSupport = cells.first().has("img[src*='command/support.png']").size() == 1;
  8716.                                         }
  8717.                
  8718.                                         request[village.coord].hasSupport = isSupport;
  8719.                                         request[village.coord].attacks.push({
  8720.                                             isSupport: isSupport,
  8721.                                             units: unitsSent,
  8722.                                             unitsString: buildAttackString(null, unitsSent, null, isSupport, user_data.command.bbCodeExport.requiredTroopAmount),
  8723.                                             commandName: isSupport ? $.trim(firstCell.text()) : "",
  8724.                                             commandId: isSupport ? firstCell.find(":checkbox").attr("value") : null,
  8725.                                             arrivalDate: getDateFromTodayTomorrowTW(cells.eq(2).text())
  8726.                                         });
  8727.                                     }
  8728.                                 }
  8729.                             });
  8730.                
  8731.                             var exportWidgets = [];
  8732.                             if ($(this).attr("id") === "BBCodeOutput") {
  8733.                                 var requestsPer500 = [""];
  8734.                                 var requestComposed = "";
  8735.                                 for (var i = 0; i < villages.length; i++) {
  8736.                                     var currentVillage = request[villages[i]];
  8737.                                     var currentText = "";
  8738.                                     currentText += "[spoiler][code]";
  8739.                                     var attackCount = 0;
  8740.                                     var supportCount = 0;
  8741.                                     var lastAttack = null;
  8742.                                     var largestAttack = 0;
  8743.                                     var totalPop = 0;
  8744.                                     for (var attackId = 0; attackId < currentVillage.attacks.length; attackId++) {
  8745.                                         var currentAttack = currentVillage.attacks[attackId];
  8746.                                         if (currentAttack.isSupport) {
  8747.                                             supportCount++;
  8748.                                             $.each(world_data.units, function (i, val) {
  8749.                                                 totalPop += currentAttack.units[val] * world_data.unitsPositionSize[i];
  8750.                                             });
  8751.                                         } else {
  8752.                                             attackCount++;
  8753.                                             if (lastAttack == null || lastAttack < currentAttack.arrivalDate) {
  8754.                                                 lastAttack = currentAttack.arrivalDate;
  8755.                                             }
  8756.                                         }
  8757.                                         if (largestAttack < currentAttack.unitsString.length) {
  8758.                                             largestAttack = currentAttack.unitsString.length;
  8759.                                         }
  8760.                                     }
  8761.                
  8762.                                     for (var attackId = 0; attackId < currentVillage.attacks.length; attackId++) {
  8763.                                         var currentAttack = currentVillage.attacks[attackId];
  8764.                                         currentText += currentAttack.unitsString;
  8765.                                         var extraTabs = (largestAttack - currentAttack.unitsString.length) / 1;
  8766.                                         if (Math.ceil(extraTabs) == extraTabs) {
  8767.                                             extraTabs = Math.ceil(extraTabs);
  8768.                                         }
  8769.                                         for (var tabs = 0; tabs < extraTabs + 1; tabs++) {
  8770.                                             currentText += " ";
  8771.                                         }
  8772.                
  8773.                                         currentText += "\t" + twDateFormat(currentAttack.arrivalDate, true) + "\n";
  8774.                                     }
  8775.                                     currentText += "[/code][/spoiler]\n";
  8776.                
  8777.                                     var headerTemplate;
  8778.                                     if (!currentVillage.hasSupport && attackCount !== 0) {
  8779.                                         headerTemplate = trans.sp.commands.exportAttackHeader;
  8780.                                     }
  8781.                                     else if (currentVillage.hasSupport && attackCount === 0) {
  8782.                                         headerTemplate = trans.sp.commands.exportDefenseHeader;
  8783.                                     } else {
  8784.                                         headerTemplate = trans.sp.commands.exportCompleteHeader;
  8785.                                     }
  8786.                
  8787.                                     requestComposed +=
  8788.                                         headerTemplate
  8789.                                             .replace("{#}", attackCount)
  8790.                                             .replace("{support#}", supportCount)
  8791.                                             .replace("{totalStack}", formatNumber(totalPop))
  8792.                                             .replace("{lastAttack}", lastAttack !== null ? twDateFormat(lastAttack, true) : "")
  8793.                                             .replace("{village}", "[village]" + villages[i] + "[/village]")
  8794.                                             + "\n " + currentText;
  8795.                
  8796.                                     // splits per 500 [ characters (limit in TW)
  8797.                                     var amountBracket = requestsPer500[requestsPer500.length - 1].match(/\[/g);
  8798.                                     if (amountBracket != null && (requestComposed.match(/\[/g).length + amountBracket.length > server_settings.allowedSquareBrackets)) {
  8799.                                         requestsPer500.push("");
  8800.                                     }
  8801.                                     requestsPer500[requestsPer500.length - 1] += requestComposed;
  8802.                                     requestComposed = "";
  8803.                                 }
  8804.                
  8805.                                 for (i = 0; i < requestsPer500.length; i++) {
  8806.                                     exportWidgets.push("<textarea cols=80 rows=10 class=restackArea>" + requestsPer500[i] + "</textarea>");
  8807.                                 }
  8808.                
  8809.                             } else {
  8810.                                 // JSON export for player support
  8811.                                 var exportAttacks = [],
  8812.                                     playerName = $.trim($("#supportPlayerName").val()),
  8813.                                     filter = playerName.length === 0
  8814.                                         ? function(attackString) { return true; }
  8815.                                         : function(attackString) { return attackString.indexOf(playerName) !== -1 };
  8816.                
  8817.                                 for (var i = 0; i < villages.length; i++) {
  8818.                                     var currentVillage = request[villages[i]];
  8819.                
  8820.                                     if (currentVillage.hasSupport) {
  8821.                                         for (var attackId = 0; attackId < currentVillage.attacks.length; attackId++) {
  8822.                                             var currentAttack = currentVillage.attacks[attackId];
  8823.                                             if (currentAttack.isSupport && filter(currentAttack.commandName)) {
  8824.                                                 exportAttacks.push({
  8825.                                                     commandName: currentAttack.commandName,
  8826.                                                     commandId: currentAttack.commandId
  8827.                                                 });
  8828.                                                 /*q(villages[i]);
  8829.                                                 q(currentVillage)
  8830.                                                 q(currentAttack);
  8831.                                                 q("---------------------");*/
  8832.                                             }
  8833.                                         }
  8834.                                     }
  8835.                                 }
  8836.                
  8837.                                 if (exportAttacks.length > 0) {
  8838.                                     exportWidgets.push("<textarea style='width: 96%' rows=10 class=restackArea>" + JSON.stringify(exportAttacks, null, 4) + "</textarea>");
  8839.                                 } else {
  8840.                                     alert(trans.sp.commands.exportNone);
  8841.                                 }
  8842.                             }
  8843.                
  8844.                             if (exportWidgets.length > 0) {
  8845.                                 if ($("#textsArea").size() == 0) {
  8846.                                     $(this).parent().parent().parent().append("<tr><td id=textsArea colspan=3></td></tr>");
  8847.                                 } else {
  8848.                                     $("#textsArea").html("");
  8849.                                 }
  8850.                                 for (var i = 0; i < exportWidgets.length; i++) {
  8851.                                     $("#textsArea").append(exportWidgets[i]);
  8852.                                 }
  8853.                                 $("#textsArea").append("<br><input type=button value='" + trans.sp.all.close + "' id=closeTextsArea>");
  8854.                                 $("#closeTextsArea").click(function() {
  8855.                                     $("#textsArea").parent().remove();
  8856.                                 });
  8857.                             }
  8858.                         });
  8859.                
  8860.                         function filterCommandRows(filterStrategy) {
  8861.                             // return true to hidethe row; false keep row visible (without reverse filter checkbox)
  8862.                             var reverseFilter = $("#defReverseFilter").is(":checked");
  8863.                             var goners = $();
  8864.                             var filter = hasGrouped ? "tr.command" : "tr:gt(0)";
  8865.                             $("#commands_table " + filter).filter(":visible").each(function () {
  8866.                                 if ($("th", this).size() != 0) {
  8867.                                     // don't do anything anymore when on the total row
  8868.                                     return;
  8869.                                 }
  8870.                                 if (!reverseFilter != !filterStrategy($(this))) {
  8871.                                     goners = goners.add($(this));
  8872.                                     $("input:eq(1)", this).val("");
  8873.                                 }
  8874.                             });
  8875.                             goners.remove();
  8876.                
  8877.                             // Show totals
  8878.                             var amountOfCommandos = $("#commands_table " + filter).size();
  8879.                             if (hasGrouped) {
  8880.                                 $("#commands_table tr.sumLine").hide();
  8881.                             } else {
  8882.                                 amountOfCommandos--;
  8883.                             }
  8884.                
  8885.                             $("#commands_table th:first").text(trans.sp.commands.tableTotal.replace("{0}", amountOfCommandos));
  8886.                             $("#amountOfAttacks").text(amountOfCommandos);
  8887.                             if ($("#amountOfAttacks").size() == 1) {
  8888.                                 $("#amountOfTargets").val("???");
  8889.                             }
  8890.                
  8891.                             $("#commands_table tr").not(":visible").find(":checkbox").prop("checked", false);
  8892.                         }
  8893.                
  8894.                         // Filter sent back, returning and cancelled commands
  8895.                         $("#filterReturning").click(function () {
  8896.                             $(this).attr("disabled", "disabled");
  8897.                             trackClickEvent("FilterReturning");
  8898.                             filterCommandRows( function (row) {
  8899.                                 var firstCellImage = $("td:first img:first", row).attr("src");
  8900.                                 return firstCellImage.indexOf("command/other_back.png") != -1
  8901.                                     || firstCellImage.indexOf("command/back.png") != -1
  8902.                                     || firstCellImage.indexOf("command/return.png") != -1
  8903.                                     || firstCellImage.indexOf("command/cancel.png") != -1;
  8904.                             });
  8905.                         });
  8906.                
  8907.                         $("#defFilterText").click(function () {
  8908.                             trackClickEvent("FilterText");
  8909.                             var compareTo = $("#defFilterTextValue").val().toLowerCase();
  8910.                             if (compareTo.length > 0) {
  8911.                                 filterCommandRows(function (row) {
  8912.                                     return row.text().toLowerCase().indexOf(compareTo) == -1;
  8913.                                 });
  8914.                             }
  8915.                         });
  8916.                
  8917.                         $("#filterspy").click(function () {
  8918.                             trackClickEvent("FilterSpy");
  8919.                             var position = $.inArray($(this).attr("id").substr(6), world_data.units);
  8920.                             filterCommandRows(function (row) {
  8921.                                 if (row.find("td").eq(position + offsetToUnits).text() == "0") {
  8922.                                     return false;
  8923.                                 }
  8924.                                 var totalScout = row.find("td").eq(position + offsetToUnits).text();
  8925.                
  8926.                                 var cell = row.find("td:eq(" + (offsetToUnits - 1) + ")");
  8927.                                 for (var i = 0; i < world_data.units.length; i++) {
  8928.                                     cell = cell.next();
  8929.                                     if (totalScout < cell.text()) {
  8930.                                         return false;
  8931.                                     }
  8932.                                 }
  8933.                                 return true;
  8934.                             });
  8935.                         });
  8936.                
  8937.                         $("#filtersnob").click(function () {
  8938.                             trackClickEvent("FilterSnob");
  8939.                             var position = $.inArray($(this).attr("id").substr(6), world_data.units) + offsetToUnits;
  8940.                             filterCommandRows(function (row) {
  8941.                                 return row.find("td").eq(position).text() == "0";
  8942.                             });
  8943.                         });
  8944.                
  8945.                         $("#filterFake").click(function () {
  8946.                             trackClickEvent("FilterFake");
  8947.                             var maxPop = user_data.command.filterFakeMaxPop;
  8948.                             filterCommandRows(function (row) {
  8949.                                 var total = 0;
  8950.                                 var cell = row.find("td:eq(" + (offsetToUnits - 1) + ")");
  8951.                                 for (var i = 0; i < world_data.units.length; i++) {
  8952.                                     cell = cell.next();
  8953.                                     total += parseInt(cell.text(), 10);
  8954.                
  8955.                                     // An attack with a noble is (almost) never a fake:
  8956.                                     if (i == world_data.units.length - 1 && cell.text() != "0") {
  8957.                                         return false;
  8958.                                     }
  8959.                
  8960.                                     if (total > maxPop) {
  8961.                                         return false;
  8962.                                     }
  8963.                                 }
  8964.                                 return true;
  8965.                             });
  8966.                         });
  8967.                
  8968.                         $("#defFilterContinent").click(function () {
  8969.                             trackClickEvent("FilterContinent");
  8970.                             var continent = parseInt($("#defFilterContinentText").val(), 10);
  8971.                             if (!isNaN(continent)) {
  8972.                                 filterCommandRows(function (row) {
  8973.                                     var village = getVillageFromCoords(row.find("td:first").text());
  8974.                                     var village2 = getVillageFromCoords(row.find("td:eq(1)").text());
  8975.                                     if (!village.isValid || !village2.isValid) {
  8976.                                         return true;
  8977.                                     }
  8978.                                     return village.continent() != continent && village2.continent() != continent;
  8979.                                 });
  8980.                             }
  8981.                         });
  8982.                
  8983.                         // Sort/group incoming attacks
  8984.                         $("#sortIt").click(function () {
  8985.                             trackClickEvent("Sort");
  8986.                             hasGrouped = true;
  8987.                             var newTable = "";
  8988.                             var targets = [];
  8989.                             var amountOfCommandos = 0;
  8990.                             var sum = $('#sortSum').is(':checked');
  8991.                             $("#filterReturning").attr("disabled", true);
  8992.                
  8993.                             $("#commands_table").find("tr:gt(0)").filter(":visible").each(function () {
  8994.                                 var target = $.trim($(".quickedit-label", this).text());
  8995.                                 var village = getVillageFromCoords(target);
  8996.                                 if (village.isValid) {
  8997.                                     amountOfCommandos++;
  8998.                                     if (targets[village.coord] == undefined) {
  8999.                                         targets.push(village.coord);
  9000.                                         targets[village.coord] = new Array();
  9001.                                     }
  9002.                                     targets[village.coord].push($(this));
  9003.                                 }
  9004.                             });
  9005.                
  9006.                             var mod = 0;
  9007.                             if (isSupport) {
  9008.                                 $.each(targets, function (i, v) {
  9009.                                     mod++;
  9010.                                     var amount = 0;
  9011.                                     var totalDef = new Array();
  9012.                                     totalDef['pop'] = 0;
  9013.                                     $.each(world_data.units, function (index, value) { totalDef[value] = 0; });
  9014.                
  9015.                                     $.each(targets[v], function (index, value) {
  9016.                                         var villageId = $("td:eq(1) a:first", value).attr("href").match(/id=(\d+)/)[1];
  9017.                                         newTable += "<tr class='command nowrap row_" + (mod % 2 == 0 ? 'b' : 'a') + (villageId == game_data.village.id ? " selected" : "") + "'>" + value.html() + "</tr>";
  9018.                                         amount++;
  9019.                
  9020.                                         var unitAmounts = $("td:gt(2)", value);
  9021.                                         $.each(world_data.units, function (iUnit, vUnit) {
  9022.                                             var amount = parseInt(unitAmounts.eq(iUnit).html(), 10);
  9023.                                             if (amount == 1) {
  9024.                                                 totalDef[vUnit] = amount;
  9025.                                             } else {
  9026.                                                 totalDef[vUnit] += amount;
  9027.                                             }
  9028.                                             totalDef['pop'] += amount * world_data.unitsSize['unit_' + vUnit];
  9029.                                         });
  9030.                                     });
  9031.                
  9032.                                     if (sum) {
  9033.                                         newTable += "<tr class='sumLine'><td align=right colspan=3><b>" + trans.sp.commands.totalRowsText.replace("{0}", amount).replace("{1}", formatNumber(totalDef['pop'])) + "&nbsp;</b></td>";
  9034.                                         $.each(world_data.units, function (iUnit, vUnit) {
  9035.                                             newTable += "<td>" + (totalDef[vUnit] == 0 ? "&nbsp;" : formatNumber(totalDef[vUnit])) + "</td>";
  9036.                                         });
  9037.                                         newTable += "</tr>";
  9038.                                     }
  9039.                                 });
  9040.                             } else {
  9041.                                 // attacks (meaning: no support commands)
  9042.                                 $.each(targets, function (i, v) {
  9043.                                     mod++;
  9044.                                     var amount = 0;
  9045.                                     var lastArrival = '';
  9046.                                     $.each(targets[v], function (index, value) {
  9047.                                         var villageId = $("td:eq(1) a:first", value).attr("href").match(/id=(\d+)/)[1];
  9048.                
  9049.                                         var currentArrival = $(value).find("td:eq(2)").text();
  9050.                                         if (lastArrival == currentArrival) {
  9051.                                             // Don't show when it's on the same second
  9052.                                             // Only practical on full second worlds really
  9053.                                             newTable += "<tr class='command nowrap row_" + (mod % 2 == 0 ? 'b' : 'a') + (villageId == game_data.village.id ? " selected" : "") + "'>";
  9054.                                             $(this).find("td").each(function (i) {
  9055.                                                 if (i == 2) {
  9056.                                                     newTable += "<td>&nbsp;</td>";
  9057.                                                 }
  9058.                                                 else if ($(this).text() == 0) {
  9059.                                                     newTable += "<td class=hidden>0</td>";
  9060.                                                 } else {
  9061.                                                     newTable += "<td>" + $(this).html() + "</td>";
  9062.                                                 }
  9063.                                             });
  9064.                                             newTable += "</tr>";
  9065.                                         }
  9066.                                         else {
  9067.                                             newTable += "<tr class='command nowrap row_" + (mod % 2 == 0 ? 'b' : 'a') + (villageId == game_data.village.id ? " selected" : "") + "'>" + value.html() + "</tr>";
  9068.                                         }
  9069.                                         lastArrival = currentArrival;
  9070.                                         amount++;
  9071.                                     });
  9072.                
  9073.                                     if (sum) {
  9074.                                         newTable += "<tr class='sumLine'><td align=right colspan=" + (3 + world_data.units.length) + ">" + amount + "&nbsp;</td></tr>";
  9075.                                     }
  9076.                                 });
  9077.                             }
  9078.                
  9079.                             var menu = $("#commands_table tr").first().html(),
  9080.                                 totalRow = $("#commands_table tr:last");
  9081.                             $("#commands_table").html("<table id='commands_table' class='vis'>" + menu + newTable + totalRow.outerHTML() + "</table>");
  9082.                             $("#selectAll").change(selectAllCheckboxes);
  9083.                
  9084.                             // total number of attacks
  9085.                             if ($("#amountOfAttacks").size() == 0) {
  9086.                                 var totalDesc = (isSupport ? trans.sp.commands.totalSupport : trans.sp.commands.totalAttack);
  9087.                                 var totalVillagesDesc = isSupport ? trans.sp.commands.totalVillagesSupport : trans.sp.commands.totalVillagesAttack;
  9088.                                 var pageSize = $("input[name='page_size']");
  9089.                                 if (pageSize.size() == 0) {
  9090.                                     pageSize = $("input[type='submit']:last");
  9091.                                     pageSize.after("<table class=vis><tr class='row_a'><th>" + totalVillagesDesc + "</th><td><input type=text size=5 value=" + targets.length + " id=amountOfTargets></td></tr><tr class='row_a'><th>" + totalDesc + ":</th><td id='amountOfAttacks'>" + amountOfCommandos + "</td></tr></table>");
  9092.                                 } else {
  9093.                                     pageSize[0].id = "amountOfTargets";
  9094.                                     pageSize.parent().prev().text(totalVillagesDesc);
  9095.                                     pageSize = pageSize.val(targets.length).parent().parent().parent();
  9096.                                     pageSize.append('<tr><th colspan=2>' + totalDesc + ':</th><td id="amountOfAttacks">' + amountOfCommandos + '</td></tr>');
  9097.                                 }
  9098.                             } else {
  9099.                                 $("#amountOfTargets").val(targets.length);
  9100.                                 $("#amountOfAttacks").text(amountOfCommandos);
  9101.                             }
  9102.                         });
  9103.                     } catch (e) { handleException(e, "overview-commands"); }
  9104.                     //console.timeEnd("overview-commands");
  9105.                 }());
  9106.             }
  9107.             // INCOMINGS OVERVIEW
  9108.             else if (location.href.indexOf('mode=incomings') > -1) {
  9109.                 (function() {
  9110.                     //console.time("overview-incomings");
  9111.                     try {
  9112.                         overviewTable = $("#incomings_table");
  9113.                         tableHandler.init("incomings_table", {
  9114.                             hasBottomTotalRow: getQueryStringParam("subtype") !== "supports"
  9115.                         });
  9116.                        
  9117.                         var table = {
  9118.                             /**
  9119.                              * Quick sort adds extra rows with the .total class
  9120.                              */
  9121.                             hasTotalRows: false,
  9122.                             /**
  9123.                              * Some buttons add extra columns
  9124.                              */
  9125.                             newColumns: {
  9126.                                 before: 0,
  9127.                                 after: 0
  9128.                             },
  9129.                             getColspan: function() {
  9130.                                 return 7 + this.newColumns.before + this.newColumns.after;
  9131.                             },
  9132.                             /**
  9133.                              * Remove the total rows so that other filters can operate again
  9134.                              */
  9135.                             fixTable: function() {
  9136.                                 if (this.hasTotalRows === true) {
  9137.                                     overviewTable.find("tr.total").remove();
  9138.                                     this.hasTotalRows = false;
  9139.                                 }
  9140.                             },
  9141.                             getVillageRows: function() {
  9142.                                 var rows = overviewTable.find("tr:gt(0)");
  9143.                                 if (tableHandler.settings.hasBottomTotalRow) {
  9144.                                     rows = rows.not("tr:last");
  9145.                                 }
  9146.                                 return rows;
  9147.                             },
  9148.                             /**
  9149.                              * Set the table total rows count correctly
  9150.                              * @param {number} rowCount
  9151.                              * @param {number} [villagesTargeted]
  9152.                              */
  9153.                             setTotals: function(rowCount, villagesTargeted) {
  9154.                                 var amountOfCommandsHeaderCell = $("tr:first", overviewTable).find("th:first"),
  9155.                                     amountOfRows = $("#amountOfRows");
  9156.                        
  9157.                                 assert(amountOfCommandsHeaderCell.length === 1, "couldn't find the command headercell");
  9158.                                 amountOfCommandsHeaderCell.html(amountOfCommandsHeaderCell.html().replace(/\(\d+\)/, "(" + rowCount + ")"));
  9159.                        
  9160.                                 if (typeof villagesTargeted !== "undefined") {
  9161.                                     if (amountOfRows.size() === 0) {
  9162.                                         var pageSize = $("input[name='page_size']");
  9163.                                         pageSize.parent().prev().text(trans.sp.commands.totalVillagesAttack);
  9164.                                         pageSize = pageSize.attr("id", "villagesTargeted").parent().parent().parent();
  9165.                                         pageSize.append('<tr><th colspan=2>' + trans.sp.incomings.amount + '</th><td id="amountOfRows">' + rowCount + '</td></tr>');
  9166.                                     } else {
  9167.                                         $("#amountOfRows").text(villagesTargeted);
  9168.                                         $("#villagesTargeted").val(villagesTargeted);
  9169.                                     }
  9170.                                 }
  9171.                             }
  9172.                         };
  9173.                
  9174.                         var columnsToFilterCount = 5;
  9175.                        
  9176.                         // build sangu menu
  9177.                         var menu = "";
  9178.                         menu += "<table width='100%' class='vis' id='sangu_menu'>";
  9179.                         menu += "<tr><th width='1%'>";
  9180.                         menu += "<input type=button id=sortIt value='" + trans.sp.incomings.dynamicGrouping + "' title='" + trans.sp.incomings.dynamicGroupingTooltip + "'>";
  9181.                         menu += "</th><th width='1%' nowrap>";
  9182.                         menu += "<input type=checkbox id=sortShowTotalRow " + (user_data.command.sumRow ? "checked" : "") + "> " + trans.sp.incomings.summation + " ";
  9183.                         menu += "<input type=button id=sortQuick value='" + trans.sp.incomings.fastGrouping + "' title='" + trans.sp.incomings.fastGroupingTooltip + "'>";
  9184.                         menu += "</th><th width='1%'>";
  9185.                        
  9186.                         menu += "<input type=button id=sortByAttackId value='" + trans.sp.incomings.sortByAttackId + "' title='" + trans.sp.incomings.sortByAttackIdTooltip + "'>";
  9187.                        
  9188.                         menu += "</th><th width='96%'>";
  9189.                         menu += "<input type=button id=filterAttack value='" + trans.sp.incomings.showNewIncomings + "'>";
  9190.                        
  9191.                         menu += "</th><th width='1%' nowrap>";
  9192.                         menu += "<input type=button id=commandsImport value='" + trans.sp.incomings.commandsImport + "' title='" + trans.sp.incomings.commandsImportTooltip + "'>";
  9193.                         menu += "</th></tr>";
  9194.                         menu += "</table>";
  9195.                        
  9196.                         // second row
  9197.                         menu += "<table width='100%' class=vis>";
  9198.                         menu += "<tr><th width='1%' nowrap>";
  9199.                        
  9200.                         menu += "<input type=checkbox id=defReverseFilter title='" + trans.sp.commands.filtersReverse + "'> " + trans.sp.commands.filtersReverseInfo + ": ";
  9201.                         menu += "</th>";
  9202.                        
  9203.                         // generate one input field/button filter with a select for the first cells
  9204.                         var defaultColumnFilters = (function() {
  9205.                             var headerCells = $("tr:first th", overviewTable),
  9206.                                 cols = [],
  9207.                                 headerCellText;
  9208.                        
  9209.                             for (i = 0; i < columnsToFilterCount; i++) {
  9210.                                 headerCellText = headerCells.eq(i).text();
  9211.                                 if (headerCellText.indexOf(" ") !== -1) {
  9212.                                     headerCellText = $.trim(headerCellText.substr(0, headerCellText.indexOf(" ")));
  9213.                                 }
  9214.                                 cols.push(headerCellText);
  9215.                             }
  9216.                             return cols;
  9217.                         }());
  9218.                        
  9219.                         /**
  9220.                         * builds a textinput+select+button filter that filters rows based on table column index
  9221.                         */
  9222.                         function buildColumnFilter() {
  9223.                             var i,
  9224.                                 defualtIndex = pers.get("incomingsColumnFilterIndex"),
  9225.                                 menu = "<th width='99%' nowrap>";
  9226.                        
  9227.                             menu += "<input type='text' size='20' id='filterColumnValue'>";
  9228.                             menu += "<select id='filterColumnIndex'>";
  9229.                             for (i = 0; i < defaultColumnFilters.length; i++) {
  9230.                                 menu += "<option value='" + i + "'"
  9231.                                     + (defualtIndex == i ? " selected" : "") + ">"
  9232.                                     + defaultColumnFilters[i] + "</option>";
  9233.                             }
  9234.                             menu += "</select>";
  9235.                             menu += "<input type='button' id='filterColumn' value='"
  9236.                                 + trans.sp.incomings.filterColumnButton + "'"
  9237.                                 + "'>";
  9238.                             menu += "</th>";
  9239.                             return menu;
  9240.                         }
  9241.                        
  9242.                         menu += buildColumnFilter();
  9243.                        
  9244.                         //menu += "<th width='97%' nowrap>";
  9245.                         //menu += "<input type=textbox size=3 id=defFilterContinentText maxlength=2><input type=button id=defFilterContinent value='" + trans.sp.commands.continentFilter + "'>";
  9246.                         //menu += "</th></tr>";
  9247.                        
  9248.                        
  9249.                         menu += "</table>";
  9250.                         overviewTable.before(menu);
  9251.                        
  9252.                         $("#filterColumnIndex").change(function() {
  9253.                             pers.set("incomingsColumnFilterIndex", $("#filterColumnIndex").val());
  9254.                         });
  9255.                        
  9256.                         // switch tooltips on reverse filter checkbox change
  9257.                         $("#defReverseFilter").change( function () {
  9258.                             var isChecked = $(this).is(":checked"),
  9259.                                 overviewTrans = trans.sp.incomings;
  9260.                        
  9261.                             //$("#").attr("title", isChecked ? overviewTrans.continentFilterTooltip : overviewTrans.continentFilterTooltipReverse);
  9262.                        
  9263.                             $("#filterColumn").attr(
  9264.                                 "title",
  9265.                                 overviewTrans.filterColumnButtonTooltip.replace(
  9266.                                     "{type}",
  9267.                                     isChecked ? overviewTrans.filterColumnButtonTooltipHide : overviewTrans.filterColumnButtonTooltipShow));
  9268.                         }).change();
  9269.                        
  9270.                        
  9271.                         // select all checkbox
  9272.                         $("#select_all").replaceWith("<input type='checkbox' id='selectAll'>");
  9273.                         $("#selectAll").change(function() {
  9274.                             var isChecked = $("#selectAll").is(":checked");
  9275.                             $("tr", overviewTable).find(":checkbox").prop("checked", isChecked);
  9276.                         });
  9277.                         // IMPORT os exported by other player
  9278.                         $("#commandsImport").click(function() {
  9279.                             if ($("#textsArea").size() == 0) {
  9280.                                 $(this).parent().parent().parent().append("<tr><td id=textsArea colspan=5></td></tr>");
  9281.                                 $("#textsArea").append(
  9282.                                     "<textarea cols=80 rows=10 id=commandImportText></textarea>"
  9283.                                         + "<br>"
  9284.                                         + "<input type=button value='" + trans.sp.incomings.commandsImport + "' id=commandsImportReal>"
  9285.                                         + "<input type=button value='" + trans.sp.all.close + "' id=closeTextsArea>");
  9286.                        
  9287.                                 $("#closeTextsArea").click(function() {
  9288.                                     $("#textsArea").parent().remove();
  9289.                                 });
  9290.                        
  9291.                                 $("#commandsImportReal").click(function() {
  9292.                                     var commandsToImport;
  9293.                                     try {
  9294.                                         commandsToImport = JSON.parse($("#commandImportText").val());
  9295.                                         var test = commandsToImport[0].commandName;
  9296.                                     }
  9297.                                     catch (e) {
  9298.                                         alert(trans.sp.incomings.commandsImportError);
  9299.                                     }
  9300.                        
  9301.                                     var amountReplaced = 0,
  9302.                                         commandsSent = [],
  9303.                                         i;
  9304.                        
  9305.                                     for (i = 0; i < commandsToImport.length; i++) {
  9306.                                         commandsSent[commandsToImport[i].commandId] = commandsToImport[i].commandName;
  9307.                                     }
  9308.                        
  9309.                                     table.getVillageRows().each(function () {
  9310.                                         var firstCell = $("td:first", this),
  9311.                                             commandId = firstCell.find(":input:first").attr("name");
  9312.                        
  9313.                                         //q("inputfield: " + firstCell.find(":input:first").length);
  9314.                                         //q("commandId = " + commandId + " in cell: " + firstCell.text());
  9315.                        
  9316.                                         //assert(commandId, "couldn't find command id inputfield");
  9317.                                         //assert(commandId.indexOf("command_ids") === 0, "inputfields have been renamed");
  9318.                                         commandId = parseInt(commandId.match(/\d+/)[0], 10);
  9319.                                         if (typeof commandsSent[commandId] !== 'undefined') {
  9320.                                             var inputField = $(':input[id^="editInput"]', firstCell);
  9321.                                             //assert(inputField.length === 1, "couldn't find the inputfield");
  9322.                                             inputField.val(commandsSent[commandId]);
  9323.                                             inputField.next().click();
  9324.                        
  9325.                                             amountReplaced++;
  9326.                                         }
  9327.                                     });
  9328.                        
  9329.                                     alert(trans.sp.incomings.commandsImportSuccess
  9330.                                         .replace("{replaced}", amountReplaced)
  9331.                                         .replace("{total}", commandsToImport.length));
  9332.                                 });
  9333.                             }
  9334.                         });
  9335.                
  9336.                         // Sort by attack id (and add extra column)
  9337.                         $("#sortByAttackId").click(function() {
  9338.                             $(this).attr("disabled", true);
  9339.                             table.fixTable();
  9340.                             table.newColumns.after += 2;
  9341.                        
  9342.                             var diffGroups = user_data.incomings.attackIdDescriptions;
  9343.                             function getFancyAttackIdDiffDescription(diff) {
  9344.                                 var i;
  9345.                                 for (i = 0; i < diffGroups.length; i++) {
  9346.                                     if (diff < diffGroups[i].minValue) {
  9347.                                         return diffGroups[i].text;
  9348.                                     }
  9349.                                 }
  9350.                        
  9351.                                 return user_data.incomings.attackIdHigherDescription;
  9352.                             }
  9353.                        
  9354.                             // new column in header
  9355.                             tableHandler.overviewTable.find("tr:first").append("<th>"+trans.sp.incomings.attackId+"</th><th>"+trans.sp.incomings.attackIdDifference+"</th>");
  9356.                        
  9357.                             var rows = table.getVillageRows();
  9358.                             rows.sortElements(function (rowA, rowB) {
  9359.                                 var a = $("input:first", rowA).attr("name").match(/\d+/)[0];
  9360.                                 var b = $("input:first", rowB).attr("name").match(/\d+/)[0];
  9361.                                 //q($("input:first", rowA).attr("name") + "=>" + a);
  9362.                                 return parseInt(a, 10) > parseInt(b, 10) ? 1 : -1;
  9363.                             });
  9364.                        
  9365.                             var previousRowAttackId = 0;
  9366.                             rows.each(function() {
  9367.                                 var attackId = parseInt($(this).find("input:first").attr("name").match(/\d+/)[0], 10),
  9368.                                     diff = 0,
  9369.                                     diffDescription = "&nbsp;";
  9370.                        
  9371.                                 if (previousRowAttackId != 0) {
  9372.                                     diff = Math.abs(attackId - previousRowAttackId);
  9373.                                     diffDescription = getFancyAttackIdDiffDescription(diff);
  9374.                                 }
  9375.                                 previousRowAttackId = attackId;
  9376.                        
  9377.                                 $(this).append("<td align=right>"+attackId+"</td><td>"+diffDescription+"</td>");
  9378.                             });
  9379.                         });
  9380.                        
  9381.                         // QUICK sort: performs faster but also freezes the screen (ie no countdowns)
  9382.                         // --> This might also be good in case the page is refreshing too often otherwise
  9383.                         $("#sortQuick").click(function () {
  9384.                             trackClickEvent("SortQuick");
  9385.                             table.fixTable();
  9386.                             table.hasTotalRows = true;
  9387.                        
  9388.                             var newTable = "";
  9389.                             var targets = [];
  9390.                             var commandCounter = 0;
  9391.                             var addTotalRow = $('#sortShowTotalRow').is(':checked');
  9392.                        
  9393.                             table.getVillageRows().each(function () {
  9394.                                 var target = $("td:eq(1)", this).text();
  9395.                                 var village = getVillageFromCoords(target);
  9396.                                 if (village.isValid) {
  9397.                                     commandCounter++;
  9398.                                     if (targets[village.coord] == undefined) {
  9399.                                         targets.push(village.coord);
  9400.                                         targets[village.coord] = [];
  9401.                                     }
  9402.                                     targets[village.coord].push($(this));
  9403.                                 }
  9404.                             });
  9405.                        
  9406.                             var mod = 0;
  9407.                             $.each(targets, function (i, v) {
  9408.                                 mod++;
  9409.                                 var rowColor = "row_" + (mod % 2 == 0 ? 'b' : 'a');
  9410.                                 var amount = 0;
  9411.                                 $.each(targets[v], function (index, row) {
  9412.                                     var villageId = row.find("td:eq(1) a:first").attr("href").match(/village=(\d+)/)[1];
  9413.                                     newTable += "<tr class='nowrap " + rowColor + "'"
  9414.                                         + (villageId == game_data.village.id ? " selected" : "") + ">"
  9415.                                         + row.html() + "</tr>";
  9416.                                     amount++;
  9417.                                 });
  9418.                        
  9419.                                 if (addTotalRow) {
  9420.                                     if (amount === 1) {
  9421.                                         newTable += "<tr class='" + rowColor + " total'><td align=right colspan=" + table.getColspan() + ">&nbsp;</td></tr>";
  9422.                                     } else {
  9423.                                         newTable += "<tr class='" + rowColor + " total'><td align=right colspan=" + table.getColspan() + "><b>" + trans.sp.incomings.amount + "&nbsp; " + amount + "</b>&nbsp; &nbsp;</td></tr>";
  9424.                                     }
  9425.                                 }
  9426.                             });
  9427.                        
  9428.                             var menu = $("tr:first", overviewTable).html();
  9429.                             var totalRow = $("tr:last", overviewTable).html();
  9430.                             overviewTable.html("<table id='incomings_table' class='vis'>" + menu + newTable + totalRow + "</table>");
  9431.                        
  9432.                             table.setTotals(commandCounter, targets.length);
  9433.                         });
  9434.                        
  9435.                        
  9436.                        
  9437.                        
  9438.                        
  9439.                         // DYNAMIC sort incoming attacks
  9440.                         $("#sortIt").click(function () {
  9441.                             table.fixTable();
  9442.                             trackClickEvent("Sort");
  9443.                        
  9444.                             var rows = table.getVillageRows();
  9445.                             rows.sortElements(function (a, b) {
  9446.                                 a = getVillageFromCoords($("td:eq(1)", a).text());
  9447.                                 b = getVillageFromCoords($("td:eq(1)", b).text());
  9448.                        
  9449.                                 return (a.x * 1000 + a.y) > (b.x * 1000 + b.y) ? 1 : -1;
  9450.                             });
  9451.                        
  9452.                             var amountOfVillages = 0;
  9453.                             var current = "";
  9454.                             rows.each(function () {
  9455.                                 var village = $("td:eq(1)", this);
  9456.                                 if (current != village.text()) {
  9457.                                     current = village.text();
  9458.                                     amountOfVillages++;
  9459.                                 }
  9460.                                 var type = amountOfVillages % 2 == 0 ? 'row_a' : 'row_b';
  9461.                        
  9462.                                 var villageId = village.find("a:first").attr("href").match(/village=(\d+)/)[1];
  9463.                                 this.className = "nowrap " + type + (villageId == game_data.village.id ? " selected" : "");
  9464.                             });
  9465.                        
  9466.                             table.setTotals(rows.size(), amountOfVillages);
  9467.                         });
  9468.                         /**
  9469.                          *
  9470.                          * @param {function} filterStrategy return true to hidethe row; false keep row visible (without reverse filter checkbox)
  9471.                          * @param {Object} options
  9472.                          */
  9473.                         function filterVillageRows(filterStrategy, options) {
  9474.                             options = $.extend({}, {
  9475.                                 checkboxReverses: true
  9476.                             }, options);
  9477.                        
  9478.                             var reverseFilter = options.checkboxReverses && $("#defReverseFilter").is(":checked"),
  9479.                                 goners = $(),
  9480.                                 villageCounter = 0;
  9481.                        
  9482.                             trackClickEvent(options.gaEventName);
  9483.                        
  9484.                             table.fixTable();
  9485.                        
  9486.                             table.getVillageRows().each(function () {
  9487.                                 var self = $(this);
  9488.                                 if (!reverseFilter != !filterStrategy(self)) {
  9489.                                     goners = goners.add(self);
  9490.                                 } else {
  9491.                                     villageCounter++;
  9492.                                 }
  9493.                             });
  9494.                             goners.remove();
  9495.                        
  9496.                             // Show totals
  9497.                             table.setTotals(villageCounter);
  9498.                         }
  9499.                        
  9500.                         // Show new attacks only
  9501.                         $("#filterAttack").click(function () {
  9502.                             var strategy = function(row) {
  9503.                                 return $.trim($("td:first", row).text()) != trans.tw.command.attack;
  9504.                             };
  9505.                        
  9506.                             filterVillageRows(strategy, {
  9507.                                 gaEventName: "FilterNewAttacks",
  9508.                                 checkboxReverses: false
  9509.                             });
  9510.                         });
  9511.                        
  9512.                        
  9513.                         // Filter rows on column 0 - 3 (command, target, origin, player)
  9514.                         $("#filterColumn").click(function() {
  9515.                             var filter = {
  9516.                                 index: $("#filterColumnIndex").val(),
  9517.                                 searchText: $.trim($("#filterColumnValue").val()).toLowerCase()
  9518.                             };
  9519.                        
  9520.                             var filterStrategy = function(row) {
  9521.                                 return row.find("td").eq(filter.index).text().toLowerCase().indexOf(filter.searchText) === -1;
  9522.                             };
  9523.                        
  9524.                             filterVillageRows(filterStrategy, {
  9525.                                 gaEventName: "column-" + $("#filterColumnIndex").text()
  9526.                             });
  9527.                         });
  9528.                
  9529.                
  9530.                     } catch (e) { handleException(e, "overview-incomings"); }
  9531.                     //console.timeEnd("overview-incomings");
  9532.                 }());
  9533.             }
  9534.            
  9535.             // make the editting groups box less wide
  9536.             // and add alternating row colors
  9537.             $("#edit_group_href").click(function () {
  9538.                 var groupTable = $("#group_list");
  9539.                 groupTable.width(300);
  9540.            
  9541.                 groupTable.find("th:first").attr("colspan", "3");
  9542.                 var mod = 0;
  9543.                 groupTable.find("tr:gt(0)").each(function () {
  9544.                     mod++;
  9545.                     $(this).addClass("row_" + (mod % 2 == 0 ? "a" : "b"));
  9546.                 });
  9547.             });
  9548.            
  9549.             // change troops overview link to active sangu page
  9550.             if (user_data.command.changeTroopsOverviewLink) {
  9551.                 var troopsOverviewLink = $("#overview_menu a[href*='mode=units']");
  9552.                 troopsOverviewLink.attr("href", troopsOverviewLink.attr("href") + "&type=own_home");
  9553.             }
  9554.            
  9555.             if (user_data.overviews.addFancyImagesToOverviewLinks) {
  9556.                 var overviewLinks = $("#overview_menu a");
  9557.                 overviewLinks.each(function(index) {
  9558.                     var overviewLink = $(this),
  9559.                         imageToAdd = "";
  9560.                    
  9561.                     switch (index) {
  9562.                         case 0:
  9563.             //              overviewLink.parent()
  9564.             //                    .css("background-image", 'url("https://www.tribalwars.vodka/graphic/icons/header.png")')
  9565.             //                  .css("background-repeat", "no-repeat")
  9566.             //                  .css("background-position", "-324px 0px")
  9567.             //                  .css("background-size", "200px Auto");
  9568.             //
  9569.             //              overviewLink.prepend("&nbsp; &nbsp;");
  9570.                             break;
  9571.                         case 1:
  9572.                             imageToAdd = "graphic/buildings/storage.png";
  9573.                             break;
  9574.                         case 2:
  9575.                             imageToAdd = "graphic/buildings/market.png";
  9576.                             break;
  9577.                         case 3:
  9578.                             if (overviewLink.parent().hasClass("selected")) {
  9579.                                 $("table.modemenu:last a", content_value).each(function(index) {
  9580.                                     imageToAdd = "";
  9581.                                     switch (index) {
  9582.                                         case 1:
  9583.                                             imageToAdd = "graphic/buildings/place.png";
  9584.                                             break;
  9585.                                         case 2:
  9586.                                             imageToAdd = "graphic/pfeil.png";
  9587.                                             break;
  9588.                                         case 3:
  9589.                                         case 4:
  9590.                                             $(this).css("opacity", "0.5");
  9591.                                             break;
  9592.                                         case 5:
  9593.                                             imageToAdd = "graphic/command/support.png";
  9594.                                             break;
  9595.                                         case 6:
  9596.                                             imageToAdd = "graphic/rechts.png";
  9597.                                             break;
  9598.                                     }
  9599.                                    
  9600.                                     if (imageToAdd !== "") {
  9601.                                         $(this).prepend("<img src='https://www.tribalwars.vodka/"+imageToAdd+"' title='"+overviewLink.text() + " &gt; " + $(this).text()+"' /> &nbsp;");
  9602.                                     }
  9603.                                 });
  9604.                             }
  9605.                            
  9606.                             imageToAdd = "graphic/unit/unit_knight.png";
  9607.                             break;
  9608.                         case 4:
  9609.                             if (overviewLink.parent().hasClass("selected")) {
  9610.                                 $("table.modemenu:last a", content_value).each(function(index) {
  9611.                                     imageToAdd = "";
  9612.                                     switch (index) {
  9613.                                         case 1:
  9614.                                             imageToAdd = "graphic/command/attack.png";
  9615.                                             break;
  9616.                                         case 2:
  9617.                                             imageToAdd = "graphic/command/support.png";
  9618.                                             break;
  9619.                                         case 3:
  9620.                                             imageToAdd = "graphic/command/return.png";
  9621.                                             break;
  9622.                                     }
  9623.                                    
  9624.                                     if (imageToAdd !== "") {
  9625.                                         $(this).prepend("<img src='https://www.tribalwars.vodka/"+imageToAdd+"' title='"+overviewLink.text() + " &gt; " + $(this).text()+"' /> &nbsp;");
  9626.                                     }
  9627.                                 });
  9628.                             }
  9629.                        
  9630.                             imageToAdd = "graphic/command/attack.png";
  9631.                             break;
  9632.                         case 5:
  9633.                             if (overviewLink.parent().hasClass("selected")) {
  9634.                                 $("table.modemenu:last a", content_value).each(function(index) {
  9635.                                     imageToAdd = "";
  9636.                                     switch (index) {
  9637.                                         case 1:
  9638.                                             imageToAdd = "graphic/command/attack.png";
  9639.                                             break;
  9640.                                         case 2:
  9641.                                             imageToAdd = "graphic/command/support.png";
  9642.                                             break;
  9643.                                     }
  9644.                                    
  9645.                                     if (imageToAdd !== "") {
  9646.                                         $(this).prepend("<img src='https://www.tribalwars.vodka/"+imageToAdd+"' title='"+overviewLink.text() + " &gt; " + $(this).text()+"' /> &nbsp;");
  9647.                                     }
  9648.                                 });
  9649.                             }
  9650.                        
  9651.                             imageToAdd = "graphic/unit/att.png";
  9652.                             break;
  9653.                         case 6:
  9654.                             imageToAdd = "graphic/buildings/main.png";
  9655.                             break;
  9656.                         case 7:
  9657.                             imageToAdd = "graphic/buildings/smith.png";
  9658.                             break;
  9659.                         case 8:
  9660.                             imageToAdd = "graphic/group_right.png";
  9661.                             overviewLink.prepend("<img src='https://www.tribalwars.vodka/"+imageToAdd+"' title='"+overviewLink.text()+"' /> &nbsp;");
  9662.                             imageToAdd = "graphic/group_left.png";
  9663.                             break;
  9664.                         case 9:
  9665.                             imageToAdd = "graphic/premium/coinbag_14x14.png";
  9666.                             overviewLink.parent().width(150);
  9667.                             break;
  9668.                     }
  9669.                     if (imageToAdd !== "") {
  9670.                         overviewLink.prepend("<img src='https://www.tribalwars.vodka/"+imageToAdd+"' title='"+overviewLink.text()+"' />&nbsp;&nbsp;");
  9671.                     }
  9672.                 });
  9673.             }
  9674.         }
  9675.  
  9676.         var logoffLink = $("#linkContainer a:last");
  9677.         if (user_data.global.duplicateLogoffLink) {
  9678.             $("#linkContainer a:first").after(" - ").after(logoffLink.clone());
  9679.         }
  9680.  
  9681.         logoffLink.before("<a target='_blank' title='"+trans.sp.sp.moreScriptsTooltip+"' href='"+server_settings.scriptsDatabaseUrl+"'>"+trans.sp.sp.moreScripts+"</a>")
  9682.             .before(" - <a target='_top' id='sanguPackageEditSettingsLink' href='"+getUrlString("screen=settings&mode=sangu")+"' title='" + trans.sp.sp.sanguLinkTitle + "'>Sangu Package</a> - ");
  9683.  
  9684.         (function() {
  9685.             var position = $("#sanguPackageEditSettingsLink").position(),
  9686.                 options = {
  9687.                     left: position.left,
  9688.                     top: ($(window).height() - 100)
  9689.                 },
  9690.                 content = {
  9691.                     body: trans.sp.sp.firstTimeRunEditSettings
  9692.                 };
  9693.  
  9694.             createFixedTooltip("sanguActivatorSettingsTooltip", content, options);
  9695.         }());
  9696.        
  9697.         (function() {
  9698.             //console.time("resourceColoring");
  9699.             try {
  9700.                 var storage = parseInt($("#storage").text(), 10);
  9701.        
  9702.                 // Color resources based on how full the storage place is
  9703.                 if (user_data.global.resources.active) {
  9704.                     $("#wood,#iron,#stone").each(function () {
  9705.                         var x = parseInt(this.innerHTML / storage * 10 - 1, 10);
  9706.                         $(this).css("background-color", user_data.global.resources.backgroundColors[x]);
  9707.                     });
  9708.                 }
  9709.        
  9710.                 // Blink full resources
  9711.                 if (user_data.global.resources.blinkWhenStorageFull) {
  9712.                     $("#wood,#iron,#stone").filter(function () {
  9713.                         return parseInt(this.innerHTML, 10) == storage;
  9714.                     }).css({ "font-weight": "bolder", "color": "black" }).fadeOut().fadeIn();
  9715.                 }
  9716.             } catch (e) { handleException(e, "resourcecoloring"); }
  9717.             //console.timeEnd("resourceColoring");
  9718.         }());
  9719.         // adjust links to incoming attacks/support
  9720.         // keep track of current amount of incomings
  9721.         if (user_data.global.incomings.editLinks || user_data.global.incomings.track) {
  9722.             (function() {
  9723.                 //console.time("incomingsindicator");
  9724.                 try {
  9725.                     var incoming = $("table.box:last"),
  9726.                         incomingAttacksLinks = $("a[href*='subtype=attacks']", incoming),
  9727.                         variableReplacer = function (text) {
  9728.                             var difference = "";
  9729.                             if (sinceLastCheckTimeNew > 0) {
  9730.                                 difference += "+" + sinceLastCheckTimeNew;
  9731.                                 if (sinceLastCheckTimeArrived > 0) {
  9732.                                     difference += " ";
  9733.                                 }
  9734.                             }
  9735.                             if (sinceLastCheckTimeArrived > 0) {
  9736.                                 difference += "-" + sinceLastCheckTimeArrived;
  9737.                             }
  9738.        
  9739.                             return text.replace("{difference}", difference)
  9740.                                 .replace("{elapsed}", lastCheckTimeElapsed)
  9741.                                 .replace("{time}", lastCheckTime)
  9742.                                 .replace("{current}", currentAmountOfIncomings
  9743.                                 .replace("{saved}", lastKnownAmountOfIncomings));
  9744.                         };
  9745.        
  9746.                     if (incomingAttacksLinks.size() > 0) {
  9747.                         if (user_data.global.incomings.editLinks) {
  9748.                             incomingAttacksLinks.attr("href", incomingAttacksLinks.attr("href") + "&page=-1&group=0");
  9749.                         }
  9750.                         if (user_data.global.incomings.track) {
  9751.                             incomingAttacksLinks.parent().css("white-space", "nowrap");
  9752.        
  9753.                             // Split current and new attacks in incomings link
  9754.                             var incomingAttacksAmountLink = incomingAttacksLinks.last();
  9755.                             var currentAmountOfIncomings = incomingAttacksAmountLink.text().match(/\d+/)[0];
  9756.                             var lastKnownAmountOfIncomings = parseInt(pers.get("lastKnownAmountOfIncomings" + game_data.player.sitter), 10) || 0,
  9757.                                 sinceLastCheckTimeNew = parseInt(pers.get("lastKnownAmountOfIncomingsAdded" + game_data.player.sitter), 10) || 0,
  9758.                                 sinceLastCheckTimeArrived = parseInt(pers.get("lastKnownAmountOfIncomingsRemoved" + game_data.player.sitter), 10) || 0;
  9759.        
  9760.                             var lastCheckTime = pers.get("lastKnownAmountOfIncomingsTime" + game_data.player.sitter);
  9761.                             var lastCheckTimeElapsed;
  9762.                             if (!lastCheckTime) {
  9763.                                 lastCheckTime = trans.sp.incomings.indicator.lastTimeCheckNotYetSet;
  9764.                                 lastCheckTimeElapsed = lastCheckTime;
  9765.                             } else {
  9766.                                 lastCheckTime = new Date().getTime() - parseInt(lastCheckTime, 10);
  9767.                                 lastCheckTimeElapsed = prettyDate(lastCheckTime);
  9768.                                 lastCheckTime = twDateFormat(new Date(lastCheckTime));
  9769.                             }
  9770.        
  9771.                             if (currentAmountOfIncomings != lastKnownAmountOfIncomings || sinceLastCheckTimeNew > 0 || sinceLastCheckTimeArrived > 0) {
  9772.                                 var newAttacks = currentAmountOfIncomings - lastKnownAmountOfIncomings;
  9773.                                 if (newAttacks > 0) {
  9774.                                     sinceLastCheckTimeNew += newAttacks;
  9775.                                     pers.set("lastKnownAmountOfIncomingsAdded" + game_data.player.sitter, sinceLastCheckTimeNew);
  9776.        
  9777.                                 } else if (newAttacks < 0) {
  9778.                                     sinceLastCheckTimeArrived -= newAttacks;
  9779.                                     pers.set("lastKnownAmountOfIncomingsRemoved" + game_data.player.sitter, sinceLastCheckTimeArrived);
  9780.                                 }
  9781.        
  9782.                                 pers.set("lastKnownAmountOfIncomings" + game_data.player.sitter, currentAmountOfIncomings);
  9783.        
  9784.                                 $("#incomings_amount").html(variableReplacer(user_data.global.incomings.indicator));
  9785.                                 incomingAttacksLinks.attr("title", variableReplacer(user_data.global.incomings.lastTimeCheckWarning));
  9786.                                 incomingAttacksLinks.fadeOut("slow").fadeIn("slow");
  9787.                             }
  9788.        
  9789.                             // extra image to set the lastCheckTime on incomings overview page
  9790.                             if (current_page.screen === "overview_villages" && current_page.mode === "incomings") {
  9791.                                 // Tooltip for first time users
  9792.                                 if (lastCheckTime == trans.sp.incomings.indicator.lastTimeCheckNotYetSet) {
  9793.                                     // show info tooltip
  9794.                                     var position = incomingAttacksAmountLink.position();
  9795.                                     var options = {
  9796.                                         left: position.left - 200,
  9797.                                         top: position.top + 35,
  9798.                                         width: 250
  9799.                                     };
  9800.                                     var content = {body: trans.sp.incomings.indicator.lastTimeCheckHintBoxTooltip.replace("{img}", "<img src='graphic/ally_forum.png'>")};
  9801.                                     createFixedTooltip("incomingsIndicatorHelp", content, options);
  9802.                                 }
  9803.        
  9804.                                 // change last incomings-check time
  9805.                                 incomingAttacksLinks.last().parent().after(
  9806.                                     "<td class='box-item' id='changeLastCheckTimeBox' style='white-space: nowrap'><a href='#' id='changeLastCheckTime'>&nbsp;"
  9807.                                         + "<img src='graphic/ally_forum.png' style='padding-top: 5px' "
  9808.                                         + "title='"+variableReplacer(user_data.global.incomings.indicatorTooltip)+"'/>&nbsp;</a></td>");
  9809.        
  9810.                                 // Set last incomings-check time
  9811.                                 $("#changeLastCheckTime").click(function() {
  9812.                                     var newCheckTime = new Date();
  9813.                                     pers.set("lastKnownAmountOfIncomingsTime" + game_data.player.sitter, newCheckTime.getTime());
  9814.                                     pers.set("lastKnownAmountOfIncomings" + game_data.player.sitter, currentAmountOfIncomings);
  9815.                                     pers.set("lastKnownAmountOfIncomingsAdded" + game_data.player.sitter, 0);
  9816.                                     pers.set("lastKnownAmountOfIncomingsRemoved" + game_data.player.sitter, 0);
  9817.        
  9818.                                     pers.setGlobal("fixedToolTip_incomingsIndicatorHelp", 1);
  9819.                                     $("#changeLastCheckTimeBox").fadeOut();
  9820.                                     window.location.href = window.location.href;
  9821.                                 });
  9822.                             }
  9823.                         }
  9824.                     } else {
  9825.                         // When there are no more incomings, stop tracking
  9826.                         if (user_data.global.incomings.track) {
  9827.                             pers.set("lastKnownAmountOfIncomings" + game_data.player.sitter, 0);
  9828.                             pers.set("lastKnownAmountOfIncomingsAdded" + game_data.player.sitter, 0);
  9829.                             pers.set("lastKnownAmountOfIncomingsRemoved" + game_data.player.sitter, 0);
  9830.                         }
  9831.                     }
  9832.        
  9833.                     // change incoming support link
  9834.                     if (user_data.global.incomings.editLinks) {
  9835.                         var incomingSupport = $("a[href*='subtype=supports']", incoming);
  9836.                         if (incomingSupport.size() > 0) {
  9837.                             if (user_data.global.incomings.editLinks) {
  9838.                                 incomingSupport.attr("href", incomingSupport.attr("href") + "&page=-1&group=0");
  9839.                             }
  9840.                         }
  9841.                     }
  9842.                 } catch (e) { handleException(e, "incomingsindicator"); }
  9843.                 //console.time("incomingsindicator");
  9844.             }());
  9845.         }
  9846.         if (server_settings.ajaxAllowed && user_data.global.visualizeFriends) {
  9847.             (function() {
  9848.                 //console.time("friends");
  9849.                 try {
  9850.                     function Friends() {
  9851.                         this.lastCheck = new Date().getTime();
  9852.                         this.online = {
  9853.                             names: "",
  9854.                             amount: 0
  9855.                         };
  9856.                         this.offlineAmount = 0;
  9857.                     }
  9858.        
  9859.                     /**
  9860.                      * Insert a 'friends' link with visual online/offline indication
  9861.                      */
  9862.                     function updateTWFriendsLink() {
  9863.                         var friendsLink = $("<a href='" + getUrlString("&screen=buddies") + "'></a>");
  9864.                         friendsLink.html(
  9865.                             trans.sp.rest.friendsOnline
  9866.                                 .replace("{friends}", friendsLink.text())
  9867.                                 .replace("{onlineimg}", "<img src='graphic/dots/green.png' />")
  9868.                                 .replace("{online#}", friends.online.amount)
  9869.                                 .replace("{offlineimg}", "<img src='graphic/dots/red.png' />")
  9870.                                 .replace("{offline#}", friends.offlineAmount)
  9871.                         );
  9872.                         if (friends.online.amount > 0) {
  9873.                             friendsLink.attr("title", trans.sp.rest.friendsOnlineTitle.replace("{playerNames}", friends.online.names.substr(1)));
  9874.                         }
  9875.                         $("#sanguPackageEditSettingsLink").before(friendsLink).before(" - ");
  9876.                     }
  9877.        
  9878.                     /**
  9879.                      * Parse the #content_value and update the friends link.
  9880.                      * Is called from ajax call.
  9881.                      * @param {string} overview the #content_value of the friends page
  9882.                      */
  9883.                     function parseFriendsTable(overview) {
  9884.                         var friendsTable = $("h3+table.vis:first", overview);
  9885.                         if (friendsTable.size() == 1) {
  9886.                             var friendRows = friendsTable.find("tr:gt(0)");
  9887.                             friendRows.each(function() {
  9888.                                 var friendName = $.trim($("a:first", this).text());
  9889.                                 var statusIndicatorImage = $("img:first", this);
  9890.                                 if( statusIndicatorImage.length > 0 ) {
  9891.                                     if (/red\.png/.test(statusIndicatorImage.attr("src"))) {
  9892.                                         friends.offlineAmount++;
  9893.                                     } else {
  9894.                                         if (friendName != game_data.player.name) {
  9895.                                             friends.online.names += ", " + friendName;
  9896.                                             friends.online.amount++;
  9897.                                         }
  9898.                                     }
  9899.                                 }
  9900.                             });
  9901.        
  9902.                             // localStorage save of online friends
  9903.                             pers.set("friendsOnline", JSON.stringify(friends));
  9904.        
  9905.                             updateTWFriendsLink();
  9906.                         }
  9907.                     }
  9908.        
  9909.                     var friends = pers.get("friendsOnline");
  9910.        
  9911.                     // check friends page only every 5 minutes (or when on friends page itself)
  9912.                     if ($("#village_link").val() == "/game.php?screen=buddies") {
  9913.                         friends = new Friends();
  9914.                         parseFriendsTable(content_value);
  9915.                     }
  9916.                     else {
  9917.                         if (friends) {
  9918.                             friends = JSON.parse(friends);
  9919.                         }
  9920.                         if (!friends || friends.lastCheck < new Date().getTime() - 1000 * 60 * 3) {
  9921.                             friends = new Friends();
  9922.                             ajax("buddies", parseFriendsTable);
  9923.                         } else {
  9924.                             updateTWFriendsLink();
  9925.                         }
  9926.                     }
  9927.                 } catch (e) { handleException(e, "friends"); }
  9928.                 //console.timeEnd("friends");
  9929.             }());
  9930.         }
  9931.        
  9932.        
  9933.        
  9934.        
  9935.         //var end_time = new Date();
  9936.         //console.timeEnd("SanguPackage");
  9937.         //q("" + pad(Math.abs(start_time.getTime() - end_time.getTime()), 3) + " -> " + location.search);
  9938.     }
  9939. }
  9940.  
  9941. if (location.href.indexOf('sangu.be') !== -1) {
  9942.     // sangu.be
  9943. (function() {
  9944.     // Check current version with version on the sangu.be site
  9945.     var lastVersion = $("#sanguPackageVersion"),
  9946.         resultBox = $("#versionCheckResult");
  9947.  
  9948.     if (lastVersion.length === 1) {
  9949.         resultBox.show();
  9950.         resultBox.css("padding", "20px");
  9951.         resultBox.css("margin", "10px");
  9952.         resultBox.css("font-size", 18);
  9953.         resultBox.css("height", 30);
  9954.         resultBox.css("text-align", "center");
  9955.  
  9956.         if ('8.128.0'.indexOf(lastVersion.text()) === 0) {
  9957.             resultBox.css("background-color", "green");
  9958.             resultBox.text("Je hebt de laatste versie!");
  9959.         } else {
  9960.             resultBox.css("background-color", "red");
  9961.             resultBox.text("Er is een nieuwe versie beschikbaar!");
  9962.         }
  9963.     }
  9964. }());
  9965.  
  9966. } else if (location.href.indexOf('tribalwars.nl') !== -1) {
  9967.     // TribalWars page
  9968. (function (func, GM_xmlhttpRequest) {
  9969. var lastCheck = sessionStorage.lastUpdateCheck,
  9970.     currentVersion = '8.128.0';
  9971.  
  9972. function displayNewVersion() {
  9973.     var a = document.createElement('a');
  9974.     var linkText = document.createTextNode(" - Sangu Package Update!");
  9975.     a.appendChild(linkText);
  9976.     a.title = "Er is een update voor het Sangu Package beschikbaar!";
  9977.     a.href = "http://sangu.be";
  9978.     a.style.color = "black";
  9979.     a.style.fontWeight = "bolder";
  9980.     a.style.backgroundColor = "yellow";
  9981.  
  9982.     document.getElementById("linkContainer").appendChild(a);
  9983. }
  9984.  
  9985. if (typeof GM_xmlhttpRequest !== "undefined") {
  9986.     if (!lastCheck) {
  9987.         sessionStorage.lastUpdateCheck = "done";
  9988.         try
  9989.         {
  9990.             // GM_xmlhttpRequest didn't work when put in sangu_ready
  9991.             GM_xmlhttpRequest({
  9992.                 method: "GET",
  9993.                 url: "http://www.sangu.be/api/sangupackageversion.php",
  9994.                 synchronous: false,
  9995.                 onload: function(response) {
  9996.                     if (response.responseText !== currentVersion) {
  9997.                         sessionStorage.lastUpdateCheck = "hasNew";
  9998.                         displayNewVersion();
  9999.                     }
  10000.                 }
  10001.             });
  10002.         }
  10003.         catch (e)
  10004.         {
  10005.             console.log("error fetching latest version number:");
  10006.             console.log(e);
  10007.         }
  10008.     } else if (lastCheck === "hasNew") {
  10009.         displayNewVersion();
  10010.     }
  10011. }
  10012.  
  10013.     var script = document.createElement('script');
  10014.     script.setAttribute("type", "application/javascript");
  10015.     if (window.mozInnerScreenX !== undefined) {
  10016.         // Firefox has troubles with renaming commands, villages, ... (it works some of the time)
  10017.         // But waiting for document.ready slows down the script so only wait for this on FF
  10018.         // An optimization could be to put document.readys only around those blocks that are
  10019.         // problematic.
  10020.         script.textContent = '$(document).ready(' + func + ');';
  10021.  
  10022.     } else {
  10023.         script.textContent = '(' + func + ')();';
  10024.     }
  10025.  
  10026.     document.body.appendChild(script); // run the script
  10027.     document.body.removeChild(script); // clean up
  10028. }(sangu_ready, (typeof GM_xmlhttpRequest === "undefined" ? undefined : GM_xmlhttpRequest)));
  10029.  
  10030.  
  10031. }
Add Comment
Please, Sign In to add comment