// ==UserScript==
// @name Sangu Package
// @author Laoujin / De Goede Fee
// @namespace sangu.be
// @description Package for Tribal Wars bullies
// @icon http://www.sangu.be/images/favicon.png
// @include https://*.tribalwars.nl/game.php?*
// @include http://sangu.be/*
// @include http://www.sangu.be/*
// @version 8.90.1
// @grant GM_xmlhttpRequest
// @downloadURL https://update.greasyfork.icu/scripts/32615/Sangu%20Package.user.js
// @updateURL https://update.greasyfork.icu/scripts/32615/Sangu%20Package.meta.js
// ==/UserScript==
//We are Sangu. You will be assimilated. Resistance is Futile.
// The not-one-file source code can be found at:
// https://github.com/SanguPackage/Script
function sangu_ready() {
//var start_time = new Date();
//console.time("SanguPackage");
var /**
* When game_data.majorVersion is different from Sangu version then activate sangu 'compatibility' mode (gray icon)
*/
sangu_version = '8.90.0',
/**
* true: popup with crash dump, false: don't show the popup
*/
sangu_crash = sangu_version.split(".").length === 4,
/**
* jQuery element of the cell (td) that contains all page specific widgets
*/
content_value = $("#content_value"),
/**
* config/ Configuration per server (nl, de). Contains stuff like ajaxAllowed, etc
*/
server_settings = {},
/**
* config/ Contains all translations except for the setting related translations in sangu_trans
*/
trans = {},
/**
* config/ Contains all user settings
*/
user_data = {},
/**
* config/ The current world configuration. settings like hasArchers, nightbonus, etc
*/
world_config = {},
/**
* config/ Contains all data for this world (resources, units, buildings, units_off, unitsSize, ...)
* What's in this variable depends on world_config.
* This variable is a complete and utter mess :)
*/
world_data = {},
/**
* Identifies the current page based on the querystring
*/
current_page = {
screen: game_data.screen,
mode: game_data.mode
},
keyCodeMap = {
8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pausebreak", 20: "capslock", 27: "escape", 32: " ",
33: "pageup", 34: "pagedown", 35: "end", 36: "home", 37: "arrow left", 38: "arrow up", 39: "arrow right", 40: "arrow down", 43: "+",
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",
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",
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",
98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111: "/", 112: "f1",
113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock",
145: "scrolllock", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'"
};
server_settings = {
/**
* Calculate how many more days we can attack in vacation mode
*/
maxSitDays: 60,
helpdeskUrl: "https://forum.tribalwars.nl/showthread.php?137674-8-11-GM-Algemeen-discussietopic-Sangu-Package",
/**
* This needs to be here for 'historical' reasons (Innogames versionchecker API remembers email on the server)
* when in 'compatibility' mode (gray sangu icon). Also used in the crash report.
*/
sangu: "sangu.be",
sanguEmail: "package@sangu.be",
/**
* More then 500 [ cannot be sent in messages or pasted in the noteblock
*/
allowedSquareBrackets: 500,
/**
* Are ajax calls allowed on this server
*/
ajaxAllowed: true,
/*
* async: true on AJAX calls is only allowed when this property is true
*/
asyncAjaxAllowed: false,
/**
* True: we add a direct link in the place to fill in coordinates. False: Show coords in an input field
*/
coordinateLinkAllowed: false,
/**
* Can we fill in the coordinates directly in the place (using the url querystring) from troops overview
*/
autoFillCoordinatesAllowed: true,
scriptsDatabaseUrl: "http://www.twscripts.nl/"
};
//$.extend(server_settings, {
// extraProperty: "yaye"
//});
/**
* Contains all translation
* The highest level is split in
* - tw = translations that should be translated by their server equivalents (they are used somewhere in the html by Innogames)
* - sp = new translations specific for the Sangu Package
*/
trans = {
tw: {
units: {
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" },
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." },
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" },
militia: "Militia"
},
all: {
today: "vandaag om",
tomorrow: "morgen om",
dateOn: "op",
timeOn: "om",
farm: "Boerderij",
wood: "Hout",
iron: "IJzer",
stone: "Leem",
groups: "Groepen",
continentPrefix: "C"
},
main: {
loyaltyHeader: "Toestemming:"
},
command: {
returnText: "Terugkeer",
attack: "Aanval",
support: "Ondersteuning",
haul: "Buit:",
abortedOperation: "Afgebroken commando",
catapultTarget: "Katapultdoel:",
buttonValue: "OK",
attackOn: "Aanval op ",
supportFor: "Ondersteuning voor ",
walkingTimeTitle: "Duur:"
},
incoming: {
defaultCommandName: "Bevel"
},
place: {
troopMovements: "Troepenbewegingen"
},
market: {
incomingTransports: "Binnenkomende transporten"
},
profile: {
title: "Profiel",
claimedBy: "Geclaimd door:",
awardsWon: "Behaalde awards"
},
overview: {
village: "Dorp",
incomingTroops: "Aankomend"
}
},
sp: {
sp: {
settings: {
reset: "De standaard Sangu Package settings herstellen",
resetAll: "Sangu Package 'fabrieksinstellingen' herstellen",
configuration: "Configuratie",
configurationFormTogglerTooltip: "Klik op de knoppen om de verschillende editeerschermen te openen",
activate: "Activeer",
deleteTooltip: "Verwijderen",
addRecord: "» Toevoegen",
exportSettings: "Instellingen exporteren",
importSettings: "Instellingen importeren",
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.",
importSettingsSuccess: "Settings zijn geïmporteerd!",
importError: "Het ziet er naar uit dat de geplakte tekst foutief is.",
importErrorContinueAnyway: "Toch importeren?"
},
donate: {
title: "Donatie",
whyWouldI: "Als je op regelmatige basis functionaliteit van het Sangu Package gebruikt, dan is dat een goede reden om een donatie te doen."
+ "
"
+ "Jouw dankbaarheid en financiële steun helpen me motiveren verder te blijven werken aan het Sangu Package.",
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 :)",
notable: "Een aantal personen hebben reeds een notabele donatie gedaan:",
buttonAmount: "Doneer €{amount}",
beer: "Trakteer me een biertje",
food: "Een spaghetti voor Wouter! (of pizza!)",
yaye: "Een nieuw IT boek voor mijn verzameling!"
},
configuration: "Sangu Package configureren (v{version})",
activatePackage: "Sangu Package activeren",
deactivatePackage: "Sangu Package deactiveren",
packageCrashTitle: "Het Sangu Package is gecrasht :)",
packageCrashTooltip: "Crash!!! Klik op de bol om het crash rapport te bekijken. "
+ "Je kan dit rapport naar ons doorsturen waardoor we het probleem mogelijk kunnen oplossen.",
packageCrash:
"Controleer eerst of er een update is! "
+ "Foutmelding: {error} "
+ "Details: "
+ ""
+ "
Je kan de bug hier melden."
+ " Of je kan de bug mailen."
+ "
Een bug waarvan we niet weten dat ie bestaat zal ook niet gefixed worden!
"
+ "
Geef zoveel mogelijk informatie mee bij het sturen van een bugrapport!!!
",
activatePackageWithCompatibility: "Sangu Package (v{version}) mogelijk incompatibel met huidige TW versie",
firstTimeRun: "Welkom! Het Sangu Package is momenteel inactief. Klik op de nieuwe {img} naast de opslagplaats hierboven om het package aan en uit te schakelen.",
firstTimeRunEditSettings: "Klik op de nieuwe 'Sangu Package' link om het package naar jouw smaak in te stellen!",
removeScriptWarning: "Niet meer tonen",
moreScripts: "Meer scripts",
moreScriptsTooltip: "Ga naar de site met alle goedgekeurde TW scripts",
sanguLinkTitle: "Het package naar jouw smaak instellen"
},
all: {
populationShort: "Pop",
population: "Populatie",
total: "Totaal",
last: "Laatste",
target: "Doel",
targetEx: "Doelwit",
more: "meer",
less: "minder",
all: "Alle",
withText: "met",
merchants: "Handelaren",
further: "verder",
closer: "dichter",
fieldsSuffix: "(F{0})",
close: "Sluiten"
},
main: {
unitsReplacement: "Eigen",
unitsOther: "Ondersteunende Eenheden",
rallyPointTroops: "troepen",
ownStackTitle: "Totale populatie van de eigen troepen",
supportingStackTitle: "Totale populatie van de ondersteunende troepen",
showHiddenDivs: "» Alle verborgen terugzetten ({amount} verborgen)",
hideDiv: "Volledig verbergen"
},
map: {
dodgeLastTagged: "Dodgetijd van de laatst getagde aanval"
},
tagger: {
openButton: "Open Tagger",
rename: "Herbenoemen",
renameTooltip: "Alle bevelen waarvan de checkbox aangevinkt is hernoemen",
incomingTroops: "Binnenkomende troepen",
arrival: "Aankomst",
arrivalIn: "Aankomst in",
sentNow: "Zojuist",
sentSeconds: "seconde(n)",
sent1Minute: "1 minuut",
sentMinutes: "minuten",
sent1Hour: "1 uur",
sentHours: "uren",
sentOn: "Verstuurtijd",
ago: "Geleden",
arrivesInNightBonus: " (NACHT!)",
tagIt: "Aanval Taggen",
checkAllSupport: "Aanvinken van alle zichtbare ondersteuning",
uncheckAllSupport: "Uitvinken van alle zichtbare ondersteuning",
tagged: "Tagged!",
dodgeTime: "Dodgetijd",
slowest: "Traagst",
slowestTip: "Traagste eenheid in het dorp",
allAbove: "Alle vroegere aanvallen aanvinken",
allBelow: "Alle latere aanvallen aanvinken",
renameTo: "Hernoemen naar: ",
switchModus: "» Alle aanvallen openen/sluiten",
checkAllAttacks: "Aanvinken van alle zichtbare aanvallen",
uncheckAllAttacks: "Uitvinken van alle zichtbare aanvallen",
activeDodgeTime: "Actieve dodgetijd (wordt op de kaart getoond)",
totalAttacksOnVillage: "Aantal aanvallen",
renameButtonShortcutTooltip: "Shortcut: CTRL + {hitkey}"
},
place: {
distance: "Afstand",
backOn: "Terug op",
onlyAttack: "1 aanval op {arrivalDateFirst} ({timeLeftFirst})",
multipleAttack: "{amount} aanvallen tussen {arrivalDateFirst} ({timeLeftFirst}) en {arrivalDateLast} ({timeLeftLast})",
changeSpeedImageTooltips: "{originalTitle} - Klik om de traagste eenheid te wijzigen"
},
jumper: {
goToMap: "Ga naar de kaart"
},
command: {
returnOn: "Terug op:",
arrival: "Aankomst",
dodgeNotFarEnough: "De dodge is niet ver genoeg!",
dodgeMinuteReturn: "(Terugkeer na {minutes})",
catapultImageTitle: "Klik om gebouw te vernietigen"
},
overviews: {
totalVillages: "Aantal dorpen:",
loadNextPage: "[volgende pagina laden]"
},
troopOverview: {
help: "Laat je looptijden uitrekenen door Sangu! "
+ "Gebruik de nieuwe kolom uiterst rechts om de looptijden tot een ingegeven doeldorp te berekenen. "
+ " Je kan de eenheden icons ({unitIcon}) aanklikken om de snelheid van de traagste eenheid te wijzigen."
+ ""
+ " Klikken wijzigt de snelheid op de huidige pagina. "
+ " Dubbelklikken wijzigt de snelheid op alle pagina's."
+ ""
+ " De cellen met de groene en rode rand geven de huidige traagst eenheid-snelheid aan."
+ "
De iconen in de 'Opdracht' kolom:"
+ " Gebruik om een rij te verwijderen."
+ " Gebruik om naar de verzamelplaats te gaan."
+ "(gebruik de middelste muisknop om in een nieuwe tab te openen)",
helpTitle: "Snel dorpen aanvallen (of ondersteunen)",
removeVillage: "Dorp verwijderen",
toThePlace: "Verzamelplaats",
setTargetVillageButton: "OK",
setTargetVillageButtonAlert: "Geef de coördinaten van het dorp dat je wil aanvallen (of ondersteunen)",
commandTitle: "Opdracht",
selectUnitSpeed: "Selecteer {0} als traagste eenheid. (Klik op deze pagina, Dubbel klik op alle pagina's.)",
nightBonus: "Nacht?",
village: "Dorp",
filterTroops: "Filter",
filterTroopsTooltip: "Toon enkel de dorpen met meer dan het aangegeven aantal eenheden",
filterPopulation: "Filter populatie",
filterPopulationTooltip: "Toon enkel de dorpen met meer/minder bevolking",
filterWalkingTime: "Filter looptijd",
filterWalkingTimeTooltip: "Toon enkel de dorpen met een langere looptijd (in uren) tot het doeldorp",
calcStack: "Bereken stack",
calcStackTooltip: "Toon de bevolking per dorp in de \"Nacht?\" kolom",
filterNoble: "Toon edels",
filterNobleTooltip: "Toon enkel de dorpen waar edels aanwezig zijn",
filterUnderAttack: "Toon onder aanval",
filterUnderAttackTooltip: "Toon enkel de dorpen die onder aanval zijn",
sort: "Sorteren",
sortTooltip: "Sorteren op looptijd tot doeldorp",
restack: "Stack BB Codes",
restackTitle: "Alle dorpen met minstens {requiredDiff}k bevolking minder dan {to}k "
+ "(configuratie wijzigbaar bij Sangu instellingen)",
cheapNobles: "Goedkope edelmannen beschikbaar",
filtersReverse: "De filtering omdraaien",
filtersReverseInfo: "Filters omdraaien",
freeTextFilter: "Tekst filter",
freeTextFilterTooltip: "Dorpen {filterType} de tekst wegfilteren",
freeTextFilterTooltipFilterTypeWith: "met",
freeTextFilterTooltipFilterTypeWithout: "zonder",
continentFilter: "Continent",
continentFilterTooltip: "Alle dorpen in continent wegfilteren",
continentFilterTooltipReverse: "Alle dorpen in continent tonen"
},
prodOverview: {
filter: "Filter",
filterFullGS: "Volle opslag",
merchantTooltip: "Vink aan om handelaren te highlighten",
merchantAmountTooltip: "Als de checkbox aangevinkt is worden dorpen met minder dan x handelaren in het rood gehighlight",
bbCodes: "BB Codes",
bbCodesInfo: "Gebruik IMG",
filterTooltip: "Dorpen die niet aan de filtercriteria voldoen verbergen",
filterTooltipReverse: "Dorpen die voldoen aan de filtercriteria highlighten",
filterFullGSTooltip: "Dorpen waarbij niet minstens 1 van de grondstoffen vol is verbergen",
filterFullGSTooltipReverse: "Dorpen waarbij minstens 1 van de grondstoffen vol is highlighten",
filterAllTooltip: "Dorpen waarbij niet minstens 1 van de grondstoffen meer/minder dan x is verbergen",
filterAllTooltipReverse: "Dorpen waarbij minstens 1 van de grondstoffen meer/minder dan x is highlighten",
filter1Tooltip: "Dorpen waarbij er nier meer/minder dan x {0} is verbergen",
filter1TooltipReverse: "Dorpen waarbij er meer/minder dan x {0} is highlighten",
tooMuch: "Teveel:",
tooMuchText: "Alle dorpen met minstens {diff}k meer grondstoffen dan {min}k",
tooLittle: "Te weinig:",
tooLittleText: "Alle dorpen met minstens {diff}k minder grondstoffen dan {min}k",
bbCodeExtraInfo: " (configuratie wijzigbaar bij Sangu instellingen)"
},
buildOverview: {
optimistic: "Optimistisch",
mark: "Duiden",
filter: "Filteren"
},
smithOverview: {
optimistic: "Optimistisch",
mark: "Duiden",
filter: "Filteren"
},
defOverview: {
stackButton: "Totalen berekenen",
stackTooltip: "Totale stack en afstanden berekenen",
stackFilter: "Filter op stack",
stackFilterTooltip: "Filter dorpen met meer/minder dan x totale stack vanbuiten het dorp",
village: "Dorp:",
distFilter: "Filter op afstand",
distFilterTooltip: "Filter alle dorpen die verder/dichter dan x velden liggen van dorp y",
stackBBCodes: "Stack BBCodes",
stackBBCodesTooltip: "Bepaal BB codes en aantal troepen voor een stack tot x populatie voor alle zichtbare dorpen",
filterNoSupport: "Zonder OS wegfilteren",
filterNoSupportTooltip: "Wegfilteren van alle dorpen waar geen ondersteuning meer zichtbaar is",
extraFiltersSupport: "Ondersteunende dorpen filters:",
extraFiltersDefense: "Ondersteuning filters:",
extraFiltersReverse: "De filtering omdraaien",
extraFiltersInfo: "Filters omdraaien",
distFilter2: "Afstand filter",
freeTextFilter: "Tekst filter",
barbarianFilter: "Barbarendorpen",
barbarianFilterTooltip: "Toon alle ondersteuningen naar barbarendorpen",
nobleFilter: "Alle edel-ondersteuning tonen",
nobleFilterRev: "Alle edel-ondersteuning wegfilteren",
spyFilter: "Alle verkenner-ondersteuning tonen",
spyFilterRev: "Alle verkenner-ondersteuning wegfilteren",
attackFilter: "Alle aanval-ondersteuning tonen",
attackFilterRev: "Alle aanval-ondersteuning wegfilteren",
supportFilter: "Alle verdediging-ondersteuning tonen",
supportFilterRev: "Alle verdediging-ondersteuning wegfilteren",
otherPlayerFilterShow: "tonen",
otherPlayerFilterHide: "wegfilteren",
otherPlayerFilterTo: "Alle ondersteuningen naar andere spelers {action}",
otherPlayerFilterFrom: "Alle ondersteuningen van andere spelers {action}",
filterTooltipVillageTypeSupporting: "Ondersteunende dorpen",
filterTooltipVillageTypeSupported: "Ondersteunde dorpen",
freeTextFilterTooltip: "{villageType} {filterType} de tekst wegfilteren",
freeTextFilterTooltipFilterTypeWith: "met",
freeTextFilterTooltipFilterTypeWithout: "zonder",
distanceFilterTooltip: "{villageType} die {filterType} dan het aangegeven aantal velden liggen wegfilteren",
distanceFilterTooltipFilterTypeCloser: "dichter",
distanceFilterTooltipFilterTypeFurther: "verder",
totalFromOtherVillages: "totaal uit andere dorpen",
totalInOtherVillages: "totaal in andere dorpen",
freeText: "Vrij tekstveld (wordt niet opgeslagen!):",
fieldsPrefix: "F{0}",
thousandSuffix: "k",
totalVillages: "Dorpen ({0})",
distanceToVillageNoneEntered: "Geef een coördinaat! (eerste tekstveld)",
distanceToVillage: "Afstand tot {0}",
filterUnderAttack: "Filter onder aanval"
},
commands: {
filterReturn: "Filter terugkeer",
filterReturnTooltip: "'Teruggestuurd' en 'Terugkeer' bevelen verbergen",
totalRows: "Somlijn",
group: "Groeperen",
totalRowsText: "{0}x OS = {1} pop",
totalVillagesSupport: "Ondersteunde dorpen:",
totalVillagesAttack: "Aangevallen dorpen:",
totalSupport: "Ondersteuningen",
totalAttack: "Aanvallen",
bbCodeExport: "BBCode Export",
bbCodeExportTooltip: "Overblijvende aanvallen exporteren",
supportPlayerExport: "Ondersteuning exporteren",
supportPlayerExportTooltip: "Geef de naam van de speler waarvoor je de ondersteuning wil exporteren "
+ "(of laat leeg om alle ondersteuningen te exporteren). Door de export als mededeling "
+ "naar de andere speler door te sturen "
+ "kan deze jouw gestuurde troepen als bevelnaam zetten. (Hij heeft daarvoor natuurlijk"
+ " ook het Sangu Package nodig :)",
filtersReverse: "De filtering omdraaien",
filtersReverseInfo: "Filters omdraaien",
freeTextFilter: "Tekst filter",
freeTextFilterTooltip: "Aanvallen {filterType} de tekst wegfilteren",
freeTextFilterTooltipFilterTypeWith: "met",
freeTextFilterTooltipFilterTypeWithout: "zonder",
nobleFilter: "Alle edelaanvallen tonen",
nobleFilterRev: "Alle edelaanvallen wegfilteren",
spyFilter: "Alle verkenneraanvallen tonen",
spyFilterRev: "Alle verkenneraanvallen wegfilteren",
tableTotal: "Bevel ({0})",
fakeFilter: "Alle fake aanvallen wegfilteren",
fakeFilterRev: "Alle fake aanvallen tonen",
continentFilter: "Continent",
continentFilterTooltip: "Alle dorpen in continent wegfilteren",
continentFilterTooltipReverse: "Alle dorpen in continent tonen",
exportAttackHeader: "{village} {#} aanvallen, laatste [b]{lastAttack}[/b]",
exportDefenseHeader: "{village} {support#} ondersteuningen voor [b]{totalStack} pop[/b]",
exportCompleteHeader: "{village} {#} aanvallen, laatste [b]{lastAttack}[/b]\n+ {support#} ondersteuningen voor [b]{totalStack} pop[/b]",
exportNone: "Geen ondersteuning gevonden!"
},
groups: {
villageFilter: "Dorpsnaam",
villageFilterTitle: "Alle dorpen met de tekst in de dorpsnaam wegfilteren",
villageFilterTitleRev: "Alle dorpen met de tekst in de dorpsnaam tonen",
pointsFilter: "Punten",
amountFilter: "Aantal",
groupNameFilter: "Groepsnaam",
amountFilterTitle: "Alle dorpen met minder groepen wegfilteren",
amountFilterTitleRev: "Alle dorpen met meer groepen wegfilteren",
pointsFilterTitle: "Alle dorpen met minder punten wegfilteren",
pointsFilterTitleRev: "Alle dorpen met meer punten wegfilteren",
farmFilterTitle: "Alle dorpen met minder populatie wegfilteren",
farmFilterTitleRev: "Alle dorpen met meer populatie wegfilteren",
groupNameFilterTitle: "Alle dorpen met de tekst in een groepsnaam wegfilteren",
groupNameFilterTitleRev: "Alle dorpen met de tekst in een groepsnaam tonen"
},
snob: {
canProduce: "Je kan meteen produceren:"
},
profile: {
twStatsMap: "TWStats Kaart",
externalPage: "(Extern)",
internalPage: "(Intern)",
conquers: "Overnames",
villages: "Dorpen:",
graphPoints: "Punten",
graphVillages: "Dorpen",
graphOD: "OD Totaal",
graphODD: "OD Verdediging",
graphODA: "OD Aanval",
graphODS: "OD Ondersteuning",
graphRank: "Rang",
graphMembers: "Leden",
graphTWMap: "TribalWarsMap.com"
},
incomings: {
dynamicGrouping: "Dynamisch groeperen",
dynamicGroupingTooltip: "Groepeert trager maar bevriest de pagina niet",
summation: "Somlijn",
fastGrouping: "Snel groeperen",
fastGroupingTooltip: "Groepeert sneller en bevriest de pagina (geen refreshs wanneer een aanval binnenkomt)",
showNewIncomings: "Toon nieuwe aanvallen",
sortByAttackId: "Sorteer op aanvalsid",
sortByAttackIdTooltip: "Een kleiner aanvalsid betekent een eerder verstuurde aanval",
amount: "Aanvallen:",
attackId: "Aanvalsid",
attackIdDifference: "Verschil",
filterColumnButton: "Kolom filter",
filterColumnButtonTooltip: "Alle bevelen waarbij de geselecteerde kolom de ingegeven tekst bevat {type}",
filterColumnButtonTooltipHide: "verbergen",
filterColumnButtonTooltipShow: "tonen",
indicator: {
lastTimeCheckHintBoxTooltip: "Klik op {img} om de laatste tijdcheck met de huidige tijd te vervangen.",
lastTimeCheckNotYetSet: "(nog niet)"
},
commandsImport: "Ondersteuning importeren",
commandsImportTooltip: "Ondersteuning naar jouw dorpen kan op jouw account ingelezen worden"
+ " door de andere speler zijn bevelen te laten exporteren via Sangu Package (Pagina Overzicht: Bevelen)"
+ " en die export hier te plakken.",
commandsImportError: "Fout bij het inladen van de ondersteuningen.\nHet zou er zoals dit moeten uitzien: \n"
+ '[{"commandName": "702|459 (speler) Zw=1 (Pop: 1)", "commandId": "7538763"}]',
commandsImportSuccess: "{replaced} van de {total} ondersteuningen zijn hernoemd."
},
rest: {
sittingAttackTill: "Aanvallen en verdedigen van dorpen niet in eigen beheer tot:",
friendsOnline: "Vrienden {friends} ({onlineimg} {online#} | {offlineimg} {offline#})",
friendsOnlineTitle: "Online: {playerNames}"
}
}
};
/**
* Log the parameter to the console (print yaye when undefined)
*/
function q(what) { console.log(typeof what === "undefined" ? "yaye" : what); }
/**
* Alert the parameter (yaye when undefined)
*/
function qa(what) { alert(typeof what === "undefined" ? "yaye" : what); }
/**
* Show crash report
*/
function sangu_alert(e, title) {
var activator = $("#sangu_activator");
activator
.attr("src", "graphic/dots/grey.png")
.attr("title", trans.sp.sp.packageCrashTooltip);
(function() {
var position = $("#storage").position(),
options = {
left: position.left - 150,
top: position.top + 35
},
content = {body: trans.sp.sp.packageCrashTooltip, title: trans.sp.sp.packageCrashTitle};
createFixedTooltip("sanguCrashTooltip", content, options);
}());
activator.click(function() {
var currentPageHtml = document.documentElement.innerHTML,
position = $("#storage").position(),
options = {
left: $(window).width() / 2 - 300,
top: position.top + 35,
width: 600,
showOnce: false
},
game_dataSubset = {
majorVersion: game_data.majorVersion,
market: game_data.market,
world: game_data.world,
sitter: game_data.player.sitter,
village_id: game_data.village.id,
player_id: game_data.player.id,
player_name: game_data.player.name,
ally_id: game_data.player.ally_id,
villages: game_data.player.villages,
premium: game_data.player.premium/*,
account_manager: game_data.player.account_manager,
farm_manager: game_data.player.farm_manager*/
},
content = {
title: trans.sp.sp.packageCrashTitle,
body: trans.sp.sp.packageCrash
.replace("{forum-url}", server_settings.helpdeskUrl)
.replace("{title}", title)
.replace(/\{error\}/g, e.message)
.replace("{page}", JSON.stringify(current_page))
.replace("{url}", document.location.href)
.replace("{version}", sangu_version)
.replace("{browser}", JSON.stringify($.browser))
.replace("{game_data}", JSON.stringify(game_dataSubset))
.replace("{stacktrace}", e.stack ? e.stack + "\n\n" + e.stacktrace : "assertion?")
.replace("{email}", server_settings.sanguEmail)
.replace("{html}", currentPageHtml)
};
createFixedTooltip("sanguCrash", content, options);
$("#crashArea").val($("#crashArea").val() + currentPageHtml);
return false;
});
for(i = 0; i < 7; i++) {
activator.fadeTo('slow', 0.2).fadeTo('slow', 1.0);
}
if (sangu_crash) {
activator.click();
}
}
/**
* Failed assertions show the crash report!
*/
function assert(shouldBeTruthy, message) {
if (!shouldBeTruthy) {
sangu_alert({message: message || "(broken assertion)"});
}
}
/**
* Show crash report
*/
function handleException(e, title) {
sangu_alert(e, title);
}
function pad(number, length) {
var str = '' + number;
while (str.length < length) {
str = '0' + str;
}
return str;
}
/**
* Gets a value from the querystring (or returns "")
* @param {string} name the name of the querystring parameter
* @param {string} [url] when omitted, the current location.url is assumed
*/
function getQueryStringParam(name, url) {
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regexS = "[\\?&]" + name + "=([^]*)";
var regex = new RegExp(regexS);
if( url == undefined && name == "village" ) {
return game_data.village.id;
} else {
var results = regex.exec(url == undefined ? window.location.href : url);
if (results == null) {
return "";
} else {
return results[1];
}
}
}
/**
* Get a TW url taking account sitting etc in account
* @param {string} url provide the url starting from &screen=
* @param {number} [villageId] when omitted the current village is assumed
*/
function getUrlString(url, villageId) {
if (url.indexOf("?") == -1) {
var link = location.href.substr(0, location.href.indexOf("?"));
link += "?village=" + (villageId ? villageId : getQueryStringParam("village"));
var isSit = getQueryStringParam("t");
if (isSit) {
link += "&t=" + isSit;
}
if (url.indexOf("=") == -1) {
return link + "&screen=" + url;
} else {
return link + "&" + url;
}
} else {
return url;
}
}
/**
* Perform an ajax call (if the server allows it)
* @param {string} screen passed to getUrlString. (start from &screen=)
* @param {function} strategy executed on success. Has parameter text (content of the parameter depends on opts.contentValue)
* @param {object} [opts] object with properties
* {false|number} [villageId] passed to getUrlString. Default is false which defaults to current village. Otherwise pass a village id.
* {boolean=true} [contentValue] true (default): only return the #content_value. false: return entire DOM HTML
* {boolean=true} [async] defaults to true
*/
function ajax(screen, strategy, opts) {
if (!server_settings.ajaxAllowed) {
alert("Ajax is not allowed on this server.");
return;
}
opts = $.extend({}, { villageId: false, contentValue: true, async: false }, opts);
$.ajax({
url: getUrlString(screen, opts.villageId),
async: server_settings.asyncAjaxAllowed ? opts.async : false,
success: function(text) {
text = opts.contentValue ? $("#content_value", text) : text;
strategy(text);
}
});
}
function spSpeedCookie(setter) {
if (setter == undefined) {
var speedCookie = pers.get("targetVillageSpeed");
if (speedCookie == '') {
speedCookie = 'ram';
}
return speedCookie;
} else {
if (setter.indexOf('_') == 4) {
setter = setter.substr(setter.indexOf('_') + 1);
}
pers.set("targetVillageSpeed", setter);
return setter;
}
}
function spTargetVillageCookie(setter) {
if (setter == undefined) {
return pers.get("targetVillageCoord");
} else {
pers.set("targetVillageCoord", setter);
return setter;
}
}
function getDistance(x1, x2, y1, y2, speed) {
var dist = {};
dist.fields = Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
dist.travelTime = dist.fields * (speed == '' ? world_data.unitsSpeed.unit_ram : world_data.unitsSpeed['unit_' + speed]);
dist.arrivalTime = getDateFromTW($("#serverTime").text(), true);
dist.arrivalTime.setTime(dist.arrivalTime.getTime() + (dist.travelTime * 60 * 1000));
dist.isNightBonus = isDateInNightBonus(dist.arrivalTime);
if (speed == 'snob' && dist.travelTime > world_config.maxNobleWalkingTime) {
dist.html = "" + twDurationFormat(dist.travelTime) + "";
dist.isNightBonus = true;
} else {
var displayTime = twDateFormat(dist.arrivalTime);
if (speed != 'merchant' && dist.isNightBonus) {
displayTime = "" + displayTime + "";
}
dist.html = user_data.walkingTimeDisplay
.replace("{duration}", twDurationFormat(dist.travelTime))
.replace("{arrival}", displayTime);
}
if (dist.fields == 0) {
dist.html = "";
}
return dist;
}
_gaq.push(['b._setAccount', 'UA-30075487-3']);
/**
* Send click to google analytics
* @param {string} action
*/
function trackClickEvent(action) {
trackEvent("ButtonClick", action);
}
/**
* google analytics event tracking
*/
function trackEvent(category, action, label) {
// category: clicks (downloads, ...)
// action: which button clicked
if (typeof label === 'undefined') {
label = getQueryStringParam("screen");
var mode = getQueryStringParam("mode");
if (mode) label += "-" + mode;
}
//_gaq.push(['b._setAccount', 'UA-30075487-3']);
//_gaq.push(['b._trackPageview']);
// _gat._getTrackerByName('b')._trackEvent("SanguPackage", "Loaded", "withGetB");
try
{
_gat._getTrackerByName('b')._trackEvent(category, action, label);
}
catch (e) {
// no crash report for this
}
}
function fillRallyPoint(units) {
var script = "";
$.each(world_data.units, function (i, v) {
if (units[v] != undefined && units[v] > 0) {
script += "document.forms[0]." + v + ".value=\"" + units[v] + "\";";
} else {
script += "document.forms[0]." + v + ".value=\"\";";
}
});
return script;
}
/**
* Tries to find village coords in str and convert it to a 'village' object
* @param {string} str the string to be converted to a village object
* @param {true|} looseMatch !!!Do not provide a value for looseMatch when converting a real village name.!!!
* It should be set to true when it was the user that provided the input str. When true,
* a str like 456-789 would also match. (so that Sangu Package users don't have to use | but can instead
* use anything to seperate the 2 coordinates).
* @returns {object} object with parameters isValid: false when the string could not be matched and true with extra properties
* x, y, coord, validName (=html friendly id name) and continent
*/
function getVillageFromCoords(str, looseMatch) {
// if str is "villageName (X|Y) C54" then the villageName could be something like "456-321"
// the regex then thinks that the villageName are the coords
// looseMatch
var targetMatch = looseMatch != undefined ? str.match(/(\d+)\D(\d+)/g) : str.match(/(\d+)\|(\d+)/g);
if (targetMatch != null && targetMatch.length > 0) {
var coordMatch = targetMatch[targetMatch.length - 1].match(/(\d+)\D(\d+)/);
var village = { "isValid": true, "coord": coordMatch[1] + '|' + coordMatch[2], "x": coordMatch[1], "y": coordMatch[2] };
village.validName = function () { return this.x + '_' + this.y; };
village.continent = function () { return this.y.substr(0, 1) + this.x.substr(0, 1); };
return village;
}
return { "isValid": false };
}
function buildAttackString(villageCoord, unitsSent, player, isSupport, minimum, haulDescription) {
var seperator = " ";
if (minimum == undefined) {
minimum = 0;
}
var totalPop = 0;
var renamed = villageCoord == null ? "" : villageCoord + seperator;
var sent = "";
$.each(world_data.units, function (i, val) {
var amount = unitsSent[val];
if (amount != 0) {
if (val == "snob") {
renamed += trans.tw.units.names[val] + "! ";
}
else if (amount >= minimum) {
sent += ", " + trans.tw.units.shortNames[val] + "=" + amount;
}
totalPop += amount * world_data.unitsPositionSize[i];
}
});
if (player) {
renamed += '(' + player + ')' + seperator;
}
if (sent.length > 2) {
sent = sent.substr(2);
}
if (isSupport) {
sent += seperator + "(" + trans.sp.all.populationShort + ": " + formatNumber(totalPop) + ")";
}
if (user_data.attackAutoRename.addHaul && typeof haulDescription !== 'undefined') {
sent += " (" + trans.tw.command.haul + " " + haulDescription + ")";
}
return renamed + sent;
}
function calcTroops(units) {
// units is an array of numbers; keys are the unit names (without unit_)
var x = {};
x.totalDef = 0;
function removeElement(arr, element) {
var idx = arr.indexOf(element);
if (idx != -1) {
arr.splice(idx, 1);
}
return arr;
}
// heavy doesn't count in determining whether the village is def/off (since you got some crazy guys using hc as offense and defense:)
$.each(removeElement(world_data.units_def, 'heavy'), function (i, v) { x.totalDef += units[v] * world_data.unitsSize['unit_' + v]; });
x.totalOff = 0;
$.each(removeElement(world_data.units_off, 'heavy'), function (i, v) { x.totalOff += units[v] * world_data.unitsSize['unit_' + v]; });
x.isDef = x.totalDef > x.totalOff;
x.isScout = units.spy * world_data.unitsSize.unit_spy > x.totalDef + x.totalOff;
x.isMatch = function (type) { return (type == 'all' || (type == 'def' && this.isDef) || (type == 'off' && !this.isDef)); };
x.getSlowest =
function () {
var slowest_unit = null;
$.each(world_data.units, function (i, v) {
if (units[v] > 0 && (slowest_unit == null || world_data.unitsSpeed["unit_" + slowest_unit] < world_data.unitsSpeed["unit_" + v])) {
slowest_unit = v;
}
});
return slowest_unit;
};
x.colorIfNotRightAttackType =
function (cell, isAttack) {
var isSet = false;
if (units.snob != undefined && units.snob > 0) {
if (isAttack) {
if (units.snob > 1) {
isSet = true;
cell.css("background-color", user_data.colors.error).css("border", "1px solid black");
cell.animate({
width: "70%",
opacity: 0.4,
marginLeft: "0.6in",
fontSize: "3em",
borderWidth: "10px"
}, 5000, function () {
// Animation complete.
});
} else {
return;
}
} else {
isSet = true;
}
}
else if (x.totalDef + x.totalOff < user_data.command.filterFakeMaxPop) {
// fake
return;
}
if (!isSet && (x.isScout || x.isMatch(isAttack ? 'off' : 'def'))) {
return;
}
cell.css("background-color", user_data.colors.error);
};
return x;
}
function stackDisplay(totalFarm, stackOptions) {
// TODO: this function is only used on main village overview
if (stackOptions == undefined) {
stackOptions = {};
}
var farmSize = game_data.village.buildings.farm * world_config.farmLimit;
var stackDesc = '' + formatNumber(totalFarm);
if (stackOptions.showFarmLimit && world_config.farmLimit > 0) {
stackDesc += ' / ' + formatNumber(farmSize);
}
if (stackOptions.percentage) {
stackDesc += ' (' + stackOptions.percentage + ')';
}
var bgColor = getStackColor(totalFarm, farmSize);
if (stackOptions.cell == undefined) {
return {
color: bgColor,
desc: stackDesc,
cssColor: "style='background-color:" + bgColor + "'"
};
} else {
if (stackOptions.appendToCell) {
stackOptions.cell.append(" » " + stackDesc);
} else {
stackOptions.cell.html(stackDesc);
}
if (!stackOptions.skipColoring) {
stackOptions.cell.css("background-color", bgColor);
}
}
}
/**
* Gets the configured stack backgroundcolor for the given stackTotal
* @param stackTotal {number}
* @returns {color}
*/
function getStackColor(stackTotal) {
var color = null,
arrayToIterate,
farmLimitModifier;
if (world_config.farmLimit > 0) {
arrayToIterate = user_data.farmLimit.acceptableOverstack;
farmLimitModifier = 30 * world_config.farmLimit;
} else {
arrayToIterate = user_data.farmLimit.unlimitedStack;
farmLimitModifier = 1; // = No modifier
}
if (arrayToIterate.length > 0) {
$.each(arrayToIterate, function (index, configValue) {
if (color == null && stackTotal < farmLimitModifier * configValue) {
if (index === 0) {
color = "";
} else {
color = user_data.farmLimit.stackColors[index - 1];
//q(stackTotal +"<"+ farmLimitModifier +"*"+ configValue);
//q("return " + (index - 1) + "->" + color);
}
return false;
}
});
if (color != null) {
//q(stackTotal + " -> " + color);
return color;
}
return user_data.farmLimit.stackColors[user_data.farmLimit.stackColors.length - 1];
}
return "";
}
var modernizr = (function () {
// Difference in capital letter with the Modernizr library
// So nothing will break should TW start making use of it
return {
localstorage: (function supports_html5_storage() {
try {
return 'localStorage' in window && window['localStorage'] !== null;
} catch (e) {
return false;
}
})()
};
})();
var pers;
(function (pers) {
function getKey(key) {
return 'sangu_' + key;
}
function getWorldKey(key) {
return 'sangu_' + game_data.world + '_' + key;
}
function getCookie(key) {
key = getWorldKey(key);
return (function() {
var x, cooks, cookie;
if (document.cookie.match(/;/)) {
cooks = document.cookie.split("; ");
for (x = 0; x < cooks.length; x++) {
cookie = cooks[x];
if (cookie.match(key + "=")) {
return cookie.replace(key + "=", "");
}
}
} else {
if (document.cookie.match(key + "=")) {
return document.cookie.replace(key + "=", "");
}
}
return '';
})();
}
function getGlobal(key) {
key = getKey(key);
if (modernizr.localstorage) {
var value = localStorage[key];
return typeof value === 'undefined' ? '' : value;
} else {
return getCookie(key);
}
}
function getSession(key) {
key = getWorldKey(key);
if (modernizr.localstorage) {
var value = sessionStorage[key];
return typeof value === 'undefined' ? '' : value;
} else {
return getCookie(key);
}
}
function get(key) {
return getGlobal(key);
}
function setCookie(key, value, expireMinutes) {
key = getWorldKey(key);
(function() {
var date_obj = new Date(),
time = date_obj.getTime();
if (typeof expireMinutes === 'undefined') {
time += 60 * 1000 * 24 * 356;
} else {
time += expireMinutes * 1000 * 60;
}
date_obj.setTime(time);
document.cookie = key + "=" + value + ";expires=" + date_obj.toGMTString() + ";";
})();
}
function setGlobal(key, value) {
key = getKey(key);
if (modernizr.localstorage) {
localStorage[key] = value;
} else {
setCookie(key, value);
}
}
function setSession(key, value) {
key = getWorldKey(key);
if (modernizr.localstorage) {
sessionStorage[key] = value;
} else {
setCookie(key, value);
}
}
function set(key, value) {
setGlobal(key, value);
}
function removeSessionItem(key) {
key = getKey(key);
if (modernizr.localstorage) {
sessionStorage.removeItem(key);
}
// fuck cookies
}
function clear() {
if (modernizr.localstorage) {
sessionStorage.clear();
localStorage.clear();
}
}
pers.removeSessionItem = removeSessionItem;
pers.getWorldKey = getWorldKey;
pers.getKey = getKey;
pers.set = set;
pers.setCookie = setCookie;
pers.setGlobal = setGlobal;
pers.setSession = setSession;
pers.get = get;
pers.getCookie = getCookie;
pers.getGlobal = getGlobal;
pers.getSession = getSession;
pers.clear = clear;
})(pers || (pers = {}));
$.fn.sortElements = (function () {
var sort = [].sort;
return function (comparator, getSortable) {
getSortable = getSortable || function () { return this; };
var placements = this.map(function () {
var sortElement = getSortable.call(this),
parentNode = sortElement.parentNode,
// Since the element itself will change position, we have
// to have some way of storing its original position in
// the DOM. The easiest way is to have a 'flag' node:
nextSibling = parentNode.insertBefore(
document.createTextNode(''),
sortElement.nextSibling
);
return function () {
if (parentNode === this) {
throw new Error("You can't sort elements if any one is a descendant of another.");
}
// Insert before flag:
parentNode.insertBefore(this, nextSibling);
// Remove flag:
parentNode.removeChild(nextSibling);
};
});
return sort.call(this, comparator).each(function (i) {
placements[i].call(getSortable.call(this));
});
};
})();
$.fn.outerHTML =
function () {
return $('
').append(this.clone()).remove().html();
};
/**
* Create a dialog box on a fixed position with self closing functionality
* @param {string} id The DOM ID of the div
* @param content .title: Short title. Defaults to Sangu Package.
* .body: The HTML to show in the body of the tooltip
* @param options .top: CSS top in px
* .left: CSS left in px
* .width: CSS width in px. Defaults to 350.
* .showOnce: If true the tooltip will never be shown again once closed. Defaults to true.
*/
function createFixedTooltip(id, content, options) {
if (typeof options.width === 'undefined') {
options.width = 350;
}
if (typeof options.showOnce === 'undefined') {
options.showOnce = true;
}
if (typeof content.title === 'undefined') {
content.title = "Sangu Package";
}
var persKey = "fixedToolTip_"+id; // Other implementations depend on this naming
if (!options.showOnce || pers.getGlobal(persKey) == '') {
content_value.after('
";
}
function createMoveableWidget(id, title, content) {
return '
'
+ title + '
' + content + '
';
}
function printCoord(village, desc) {
if (server_settings.coordinateLinkAllowed) {
return "" + desc + "";
} else {
return "" + desc + " ";
}
}
// Activate / deactivate the tool
var isSanguActive = pers.get("sanguActive") == "true";
if (location.href.indexOf('changeStatus=') > -1) {
isSanguActive = location.href.indexOf('changeStatus=true') > -1;
pers.set("sanguActive", isSanguActive);
pers.setGlobal("fixedToolTip_sanguActivatorTooltip", 1);
}
var activatorImage = isSanguActive ? "green" : 'red';
var activatorTitle = (!isSanguActive ? trans.sp.sp.activatePackage : trans.sp.sp.deactivatePackage) + " (v" + sangu_version + ")";
function isSanguCompatible() {
return sangu_version.indexOf(game_data.majorVersion) === 0;
}
// Check for new version
var loginMonitor = pers.get("sanguLogin");
if (typeof GM_xmlhttpRequest !== "undefined" && !isSanguCompatible() && loginMonitor !== '') {
var parts = loginMonitor.match(/(\d+)/g);
if (parseInt(parts[2], 10) != (new Date()).getDate()) {
GM_xmlhttpRequest({
method: "GET",
url: "http://www.sangu.be/api/sangupackageversion.php",
onload: function (response) {
console.log(response.status, response.responseText.substring (0, 80));
}
});
}
}
if (pers.get("forceCompatibility") === '' || pers.get("forceCompatibility") === 'false') {
if (isSanguActive) {
// Check compatibility with TW version
if (!isSanguCompatible()) {
try {
ScriptAPI.register('Sangu Package', sangu_version, 'Laoujin', server_settings.sanguEmail);
} catch (e) {
$("#script_list a[href$='mailto:"+server_settings.sanguEmail+"']").after(" "+trans.sp.sp.removeScriptWarning+"");
$("#removeScriptWarning").click(function() {
pers.set("forceCompatibility", "true");
});
}
}
}
// gray icon when tw version doesn't match
if (!isSanguCompatible()) {
activatorImage = "grey";
activatorTitle = trans.sp.sp.activatePackageWithCompatibility.replace("{version}", sangu_version);
}
}
$("#storage").parent()
.after(
"
");
// First time run message - Position beneath resource/storage display
if (!isSanguActive) {
(function() {
var position = $("#storage").position(),
options = {
left: position.left - 150,
top: position.top + 35
},
content = {body: trans.sp.sp.firstTimeRun.replace("{img}", "")};
createFixedTooltip("sanguActivatorTooltip", content, options);
}());
} else {
(function() {
var position = $("#storage").position(),
options = {
left: position.left - 150,
top: position.top + 35
},
content = {body: 'Sangu stelt zijn nieuwste tool voor: TW Tactics!
Probeer het zeker eens voordat je vijanden het tegen jou beginnen te gebruiken!'};
createFixedTooltip("twTacticsTooltip", content, options);
}());
}
// User config
// Configure the Sangu Package
/*
user_data = pers.get('sangusettings');
if (user_data !== '') {
user_data = JSON.parse(user_data);
} else {*/
user_data = {
proStyle: true,
displayDays: false, /* true: display (walking)times in days when > 24 hours. false: always displays in hours */
walkingTimeDisplay: "{duration} || {arrival}",
colors: {
error: "#FF6347",
good: "#32CD32",
special: "#00FFFF",
neutral: "#DED3B9"
},
global: {
resources: {
active: true, /* All pages: true/false: color the resources based on how much the storage is filled */
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 */
blinkWhenStorageFull: true /* All pages: Blink the resources if the storage is full */
},
incomings: {
editLinks: true, /* All pages: Edit the incoming attacks/support links: add "show all groups" and "show all pages" to the original links */
track: true,
indicator: "({current} {difference})",
indicatorTooltip: "Laatste tijdcheck: {elapsed} geleden",
lastTimeCheckWarning: "Aanvallen: {difference}. Laatste tijdcheck: {elapsed} geleden"
},
visualizeFriends: true,
duplicateLogoffLink: false
},
scriptbar: {
editBoxCols: 700,
editBoxRows: 12
},
main: {
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'] */
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 */
ajaxLoyalty: true /* Get the loyalty at the building construction/destruction page */
},
other: {
calculateSnob: true, /* nobles: calculates how many nobles can be produced immediately */
reportPublish: ["own_units", "own_losses", "opp_units", "opp_losses", "carry", "buildings", "own_coords", "opp_coords", "belief"] /* Publishing report: automatically check the 'show' checkboxes */
},
market: {
resizeImage: true,
autoFocus: true
},
farmLimit: {
stackColors: ['#DED3B9', '#3CB371', '#FF6347'],
acceptableOverstack: [0.5, 1.2, 1.35], /* Different pages: % of acceptable overstack (only relevant for farmlimit worlds) */
unlimitedStack: [24000, 60000, 100000] /* Different pages: Calculate stacks based on total troops (for non farmlimit worlds) */
},
command: { /* features for the own troops overview page */
changeTroopsOverviewLink: true, /* Change the link to the own troops overview */
middleMouseClickDeletesRow2: false, /* Let the new default overwrite the old one */
filterMinPopulation: 18000, /* Default number filled in to filter on village stack */
filterMinDefaultType: 'axe', /* This unit type is by default selected in the filter dropdown */
filterMinDefault: 5000, /* The default number filled in to filter on troop amounts */
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 */
filterMinOther: 5000, /* Use this number as the default when the unit is not present in filterMin */
filterAutoSort: true, /* Automatically sort the list on walking distance when entering a target village */
/* These features apply to the commands overview page */
sumRow: true, /* Add a totalrow between different villages */
filterFakeMaxPop: 300, /* Commands fake filter: Everything below 300 pop is considered a fake attack */
bbCodeExport: { /* BB code export */
requiredTroopAmount: 100
}
},
incomings: {
attackIdDescriptions: [
{minValue: 10, text: " "},
{minValue: 50, text: "10-50"},
{minValue: 100, text: "50-100"},
{minValue: 200, text: "100-200"},
{minValue: 500, text: "200-500"},
{minValue: 1000, text: "500-1000"},
{minValue: 5000, text: "1000-5000"}
],
attackIdHigherDescription: "5000+"
},
overviews: {
addFancyImagesToOverviewLinks: true
},
incoming: { /* Features for the built in TW tagger */
autoOpenTagger: true, /* Open the tagger automatically if the incoming attack has not yet been renamed */
forceOpenTagger: true, /* Always open the tagger automatically */
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. */
villageBoxSize: 600, /* Adjust the width of the table with the village information (support for 2-click) */
invertSort: true /* true=noblemen at the top and scouts at the bottom of the table */
},
overview: { /* The default village overview page */
ajaxSeperateSupport: true, /* Village overview: Seperate own and supported troops */
ajaxSeperateSupportStacks: true, /* Village overview: Calculate stacks for own and supported troops */
canHideDiv: true
},
mainTagger2: {
active: true,
autoOpen: true,
inputBoxWidth: 300,
defaultDescription: "{xy} OK",
otherDescs:
[
{ active: true, name: "Dodgen", hitKey: "D", renameTo: "{xy}----------------------------------------- DODGE THIS" },
{ active: true, name: "Nacht", hitKey: "N", renameTo: "{xy} NIGHTBONUS" },
{ active: true, name: "Check stack", hitKey: "P", renameTo: "{xy}----------------------------------------- CHECK STACK" },
{ active: true, name: "Timen!", hitKey: "T", renameTo: "{xy}***************************************** TIME IT!" },
{ active: true, name: "Edelen!", hitKey: "E", renameTo: "{xy}----------------------------------------- NOBLE!!" },
{ active: false, name: "Leeg1", hitKey: "L", renameTo: "It has to be, automatically" },
{ active: false, name: "Leeg2", hitKey: "U", renameTo: "Check it out, you'd better work it out" },
{ active: false, name: "Leeg3", hitKey: "Y", renameTo: "Change to another route" },
{ active: false, name: "Leeg4", hitKey: "O", renameTo: "My techniques, strategies, abilities" }
],
keepReservedWords: true,
reservedWords: [
"Edel.", "Edelman",
"Ram", "Kata.", "Katapult",
"Zcav.", "Zware cavalerie",
"Lcav.", "Lichte Cavalerie", "Bereden boog", "Bboog.",
"Verk.", "Verkenner",
"Bijl", "Zwaard", "Speer", "Boog",
"Ridder"
],
autoOpenCommands: false,
minutesDisplayDodgeTimeOnMap: 3,
minutesWithoutAttacksDottedLine: 3 * 60,
colorSupport: '#FFF5DA' /* Main village overview: give incoming support a different background color */
},
villageInfo4: [
{
/* On info_village page add extra link to attack. */
active: true,
off_link: {
name: "Aanvalleuh!",
group: 0,
filter: {
active: true,
unit: "axe",
amount: 5000
},
sort: true,
changeSpeed: "ram",
icon: "graphic/unit/unit_knight.png"
},
def_link: {
name: "Verdedigen!",
group: 0,
filter: {
active: true,
unit: "spear",
amount: 4000
},
sort: true,
changeSpeed: "spear",
icon: "graphic/command/support.png"
}
},
{
/* On info_village page add extra link to attack. */
active: false,
off_link: {
name: "» off2",
group: 0,
filter: {
active: false,
unit: "axe",
amount: 4000
},
sort: true,
changeSpeed: "ram"
},
def_link: {
name: "» Snelle Os!",
group: 0,
filter: {
active: true,
unit: "spear",
amount: 1000
},
sort: true,
changeSpeed: "spear"
}
}
],
resources: {
requiredResDefault: 250000,
requiredMerchants: 50,
filterMerchants: true,
filterRows: false,
bbcodeMinimumDiff: 50000,
highlightColor: "#FF7F27"
},
jumper: {
enabled: true,
autoShowInputbox: false
},
attackAutoRename: {
active: true,
addHaul: false
},
confirm: {
addExtraOkButton: false,
replaceNightBonus: true,
replaceTribeClaim: true,
addCatapultImages: true
},
place: {
attackLinks: {
scoutVillage: 100,
scoutPlaceLinks: [5, 100, 500],
scoutPlaceLinksName: "Scout{amount}",
fakePlaceLink: true,
fakePlaceExcludeTroops: [],
fakePlaceLinkName: "Fake",
noblePlaceLink: true, /* (de)Activate all noble links */
noblePlaceLinkFirstName: "NobleFirst", /* Name for the first noble which has most troops */
noblePlaceLinkSupportName: "NobleMin", /* snob with only minimal support */
noblePlaceLinksForceShow: true, /* Show NobleMin also where is only one 1 snob in the village */
nobleSupport: [
{ amount: 50, unit: 'light', villageType: 'off' },
{ amount: 50, unit: 'heavy', villageType: 'def'}
],
noblePlaceLinkDivideName: "NobleDivide",
noblePlaceLinkDivideAddRam: false /* false: Rams are not sent along with NobleDivide */
},
customPlaceLinks:
[
// use minus zero numbers to leave so many units at home
{ active: true, type: 'def', name: 'AllDef', spear: 25000, heavy: 5000, archer: 25000, sword: 25000, sendAlong: 0 },
{ active: true, type: 'def', name: '1/2-Zc', spear: 4000, heavy: 1000, sendAlong: 500 },
{ 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 },
{ active: true, type: 'off', name: 'Bijl', spear: 25000, axe: 25000, spy: 1, light: 5000, heavy: 5000, marcher: 5000, sendAlong: 0 },
{ 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] },
{ active: false, type: 'def', name: 'AlleDef', spear: 25000, sword: 25000, heavy: 5000, archer: 25000, sendAlong: 0 },
{ active: false, type: 'def', name: '3deZc', spear: 2500, heavy: 650, sendAlong: 0 },
{ active: false, type: 'def', name: '4deZc', spear: 2000, heavy: 500, sendAlong: 0 },
{ active: false, type: 'def', name: 'HelftZw', spear: 5000, sword: 5000, sendAlong: 500 },
{ active: false, type: 'def', name: '3deZw', spear: 3300, sword: 3300, sendAlong: 0 },
{ active: false, type: 'def', name: '4deZw', spear: 2500, sword: 2500, sendAlong: 0 }
]
},
/**
* units_support_detail: options on the 2 def troop overview pages
* on bbcode restack - and others!!)
*/
restack: {
to: 72000,
requiredDifference: 1000,
fieldsDistanceFilterDefault: 30,
filterReverse: true,
autohideWithoutSupportAfterFilter: true,
calculateDefTotalsAfterFilter: true,
defaultPopulationFilterAmount: 80000, /* this isn't related to restack */
removeRowsWithoutSupport: false
},
showPlayerProfileOnVillage: false,
profile: {
show: true,
moveClaim: true,
mapLink: {
show: true,
fill: '#000000',
zoom: '200',
grid: true,
playerColor: '#ffff00',
tribeColor: '#0000FF',
centreX: 500,
centreY: 500,
ownColor: '#FFFFFF',
markedOnly: true,
yourTribeColor: "#FF0000"
},
playerGraph: [["points", false], ["villages", false], ["od", false], ["oda", false], ["odd", false], ["rank", false]], // small / big / false
tribeGraph: [["points", false], ["villages", false], ["od", false], ["oda", false], ["odd", false], ["rank", false], ["members", 'big', true]],
twMapPlayerGraph: { player: [true, true], p_player: [false, false], oda_player: [true, false], odd_player: [true, false], ods_player: [true, false] },
twMapTribeGraph: { tribe: [true, true], p_tribe: [false, false], oda_tribe: [true, false], odd_tribe: [true, false] },
popup: {
show: true,
width: 900,
height: 865,
left: 50,
top: 50
}
},
smithy:
[
['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]}],
['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]}],
['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]}]
],
buildings: {
main: [20, 20],
barracks: [25, 25],
stable: [20, 20],
garage: [1, 5],
church: [0, 1],
church_f: [0, 1],
snob: [1, 3],
smith: [20, 20],
place: [1, 1],
statue: [0, 1],
market: [10, 20],
wood: [30, 30],
stone: [30, 30],
iron: [30, 30],
farm: [30, 30],
storage: [30, 30],
hide: [0, 10],
wall: [20, 20]
}
};
//}
(function() {
var saved_data = pers.get('sangusettings');
if (saved_data !== '') {
user_data = $.extend(true, user_data, JSON.parse(saved_data));
}
}());
if (isSanguActive) {
// world config: global game settings
world_config = {
hasMilitia: false,
nightbonus: {
active: false,
from: 0,
till: 0
},
smithyLevels: true,
hasChurch: false,
hasArchers: false,
hasKnight: false,
speed: 1,
unitSpeed: 1,
farmLimit: 0,
minFake: 0,
hasMinFakeLimit: false,
coins: false,
maxNobleWalkingTime: 999
};
if (pers.get('worldconfig') !== '') {
world_config = JSON.parse(pers.get("worldconfig"));
} else {
// load new world through tw API
if (server_settings.ajaxAllowed) {
function world_config_setter_unit(configBag, unitInfoXml) {
configBag.hasMilitia = $("config militia", unitInfoXml).length !== 0;
}
function world_config_setter(configBag, infoXml) {
configBag.nightbonus = {
active: $("night active", infoXml).text() === "1",
from: parseInt($("night start_hour", infoXml).text(), 10),
till: parseInt($("night end_hour", infoXml).text(), 10)
};
configBag.smithyLevels = $("game tech", infoXml).text() === "1" || $("game tech", infoXml).text() === "0";
configBag.hasChurch = $("game church", infoXml).text() !== "0";
configBag.hasArchers = $("game archer", infoXml).text() !== "0";
configBag.hasKnight = $("game knight", infoXml).text() !== "0";
configBag.speed = parseFloat($("config speed", infoXml).text());
configBag.unitSpeed = parseFloat($("config unit_speed", infoXml).text());
configBag.farmLimit = parseInt($("game farm_limit", infoXml).text(), 10);
configBag.minFake = parseInt($("game fake_limit", infoXml).text(), 10) / 100;
configBag.hasMinFakeLimit = configBag.minFake > 0;
configBag.coins = $("snob gold", infoXml).text() === "1";
configBag.maxNobleWalkingTime = parseInt($("snob max_dist", infoXml).text(), 10) * configBag.speed * configBag.unitSpeed;
}
function world_config_getter(world) {
// world nl: http://nl16.tribalwars.nl/
// world de: http://de90.die-staemme.de/
if (typeof world === 'undefined') world = '';
var world_config = {};
$.ajax({
url: world + "interface.php?func=get_unit_info",
async: false,
success: function(xml) {
world_config_setter_unit(world_config, xml);
}
});
$.ajax({
url: world + "interface.php?func=get_config",
async: false,
success: function(xml) {
world_config_setter(world_config, xml);
}
});
return world_config;
}
world_config = world_config_getter();
} else {
// Not allowed to get data with ajax: need to store the configuration here
//world_config = (function() {
// paste the world configurations for all worlds on servers that disallow ajax
//})();
alert("No configurations present for this server! (see world_config.js)\nContinueing with default settings.");
}
pers.set("worldconfig", JSON.stringify(world_config));
}
// world config
// RESOURCES
world_data.resources = ['holz', 'lehm', 'eisen'];
world_data.resources_en = ['wood', 'stone', 'iron'];
// BUILDINGS
world_data.buildingsSize =
[
["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]],
["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]],
["stable", [8, 9, 11, 13, 15, 18, 21, 24, 28, 33, 38, 45, 53, 62, 72, 84, 99, 115, 135, 158]],
["garage", [8, 9, 11, 13, 15, 18, 21, 24, 28, 33, 38, 45, 53, 62, 72]],
["snob", [80, 94, 110]],
["smith", [20, 23, 27, 32, 37, 44, 51, 60, 70, 82, 96, 112, 132, 154, 180, 211, 247, 289, 338, 395]],
["place", [0]],
["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]],
["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]],
["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]],
["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]],
["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]],
["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]],
["hide", [2, 2, 3, 3, 4, 4, 5, 6, 7, 8]],
["wall", [5, 6, 7, 8, 9, 11, 13, 15, 18, 21, 24, 28, 33, 38, 45, 53, 62, 72, 84, 99]]
];
world_data.buildingsPoints =
[
["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]],
["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]],
["stable", [20, 24, 29, 35, 41, 50, 60, 72, 86, 103, 124, 149, 178, 214, 257, 308, 370, 444, 532, 639]],
["garage", [24, 29, 35, 41, 50, 60, 72, 86, 103, 124, 149, 178, 214, 257, 308]],
["snob", [512, 614, 737]],
["smith", [19, 23, 27, 33, 39, 47, 57, 68, 82, 98, 118, 141, 169, 203, 244, 293, 351, 422, 506, 607]],
["place", [0]],
["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]],
["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]],
["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]],
["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]],
["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]],
["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]],
["hide", [5, 6, 7, 9, 10, 12, 15, 18, 21, 26]],
["wall", [8, 10, 12, 14, 17, 20, 24, 29, 34, 41, 50, 59, 71, 86, 103, 123, 148, 177, 213, 256]]
];
world_data.buildings = ["main", "barracks", "stable", "garage"];
if (world_config.hasChurch) {
world_data.buildingsSize.push(["church", [5000, 7750, 12013]]);
world_data.buildingsSize.push(["church_f", [5]]);
world_data.buildings.push("church");
}
world_data.buildings = world_data.buildings.concat(["snob", "smith", "place"]);
if (world_config.hasKnight) {
world_data.buildingsSize.push(["statue", [10]]);
world_data.buildingsPoints.push(["statue", [24]]);
world_data.buildings.push("statue");
}
world_data.buildings = world_data.buildings.concat(["market", "wood", "stone", "iron", "farm", "storage", "hide", "wall"]);
// UNITS
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 };
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 };
world_data.units_def = ["spear", "sword", "heavy"];
world_data.units_off = ["axe", "light", "heavy"];
if (!world_config.hasArchers && !world_config.hasKnight) {
world_data.unitsPositionSize = [1, 1, 1, 2, 4, 6, 5, 8, 100];
world_data.units = ["spear", "sword", "axe", "spy", "light", "heavy", "ram", "catapult", "snob"];
} else {
world_data.units = ["spear", "sword", "axe"];
world_data.unitsPositionSize = [1, 1, 1];
if (world_config.hasArchers) {
world_data.units_off.push("marcher");
world_data.units_def.push("archer");
$.extend(world_data.unitsSize, { "unit_archer": 1 }, { "unit_marcher": 5 });
$.extend(world_data.unitsSpeed, { "unit_archer": 18 }, { "unit_marcher": 10 });
world_data.units.push("archer");
world_data.unitsPositionSize.push(1);
}
world_data.units.push("spy");
world_data.unitsPositionSize.push(2);
world_data.units.push("light");
world_data.unitsPositionSize.push(4);
if (world_config.hasArchers) {
world_data.units.push("marcher");
world_data.unitsPositionSize.push(5);
}
world_data.units.push("heavy");
world_data.unitsPositionSize.push(6);
world_data.units.push("ram");
world_data.unitsPositionSize.push(5);
world_data.units.push("catapult");
world_data.unitsPositionSize.push(8);
if (world_config.hasKnight) {
$.extend(world_data.unitsSize, { "unit_knight": 10 });
$.extend(world_data.unitsSpeed, { "unit_knight": 10 });
world_data.units.push("knight");
world_data.unitsPositionSize.push(10);
}
world_data.units.push("snob");
world_data.unitsPositionSize.push(100);
}
// Unit speed adjustments
world_config.maxNobleWalkingTime *= world_data.unitsSpeed.unit_snob;
var speedModifier = world_config.speed * world_config.unitSpeed;
if (speedModifier != 1) {
$.each(world_data.unitsSpeed, function (index, value) {
world_data.unitsSpeed[index] = world_data.unitsSpeed[index] / speedModifier;
});
}
/**
* Adds a . thousands separator
*/
function formatNumber(nStr) {
nStr += '';
var x = nStr.split('.');
var x1 = x[0];
var x2 = x.length > 1 ? '.' + x[1] : '';
var rgx = /(\d+)(\d{3})/;
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + '.' + '$2');
}
return x1 + x2;
}
// DATETIME FUNCTIONS
function prettyDate(diff, showSeconds) {
diff = diff / 1000;
if (diff < 0) {
return " ";
}
if (diff < 60) {
if (showSeconds) {
return diff + " " + trans.sp.tagger.sentSeconds;
}
return trans.sp.tagger.sentNow;
}
if (diff < 120) {
return trans.sp.tagger.sent1Minute;
}
if (diff < 3600) {
return Math.floor(diff / 60) + " " + trans.sp.tagger.sentMinutes;
}
if (diff < 7200) {
return trans.sp.tagger.sent1Hour + ", " + Math.floor((diff - 3600) / 60) + " " + trans.sp.tagger.sentMinutes;
}
return Math.floor(diff / 3600) + " " + trans.sp.tagger.sentHours + ", " + Math.floor((diff % 3600) / 60) + " " + trans.sp.tagger.sentMinutes;
}
function twDurationFormat(num) {
var days = 0;
if (user_data.displayDays) {
days = Math.floor(num / 1440);
}
num -= days * 1440;
var hours = Math.floor(num / 60);
num -= hours * 60;
var mins = Math.floor(num);
num -= mins;
var secs = Math.round(num * 60);
if (days > 0) {
return days + '.' + pad(hours, 2) + ':' + pad(mins, 2) + ':' + pad(secs, 2);
} else {
return pad(hours, 2) + ':' + pad(mins, 2) + ':' + pad(secs, 2);
}
}
function twDateFormat(dat, alwaysPrintFullDate, addYear) {
var day = dat.getDate();
var cur = new Date().getDate();
if (!alwaysPrintFullDate && day == cur) {
return trans.tw.all.today + " " + pad(dat.getHours(), 2) + ':' + pad(dat.getMinutes(), 2) + ':' + pad(dat.getSeconds(), 2);
}
else if (!alwaysPrintFullDate && day == cur + 1) {
return trans.tw.all.tomorrow + " " + pad(dat.getHours(), 2) + ':' + pad(dat.getMinutes(), 2) + ':' + pad(dat.getSeconds(), 2);
}
else if (addYear) {
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() + ")";
} else {
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() + ")";
}
}
function getTimeFromTW(str) {
// NOTE: huh this actually returns the current date
// with some new properties with the "str" time
//17:51:31
var timeParts = str.split(":");
var seconds = timeParts[2];
var val = {};
val.hours = parseInt(timeParts[0], 10);
val.minutes = parseInt(timeParts[1], 10);
if (seconds.length > 2) {
var temp = seconds.split(".");
val.seconds = parseInt(temp[0], 10);
val.milliseconds = parseInt(temp[1], 10);
} else {
val.seconds = parseInt(seconds, 10);
}
val.totalSecs = val.seconds + val.minutes * 60 + val.hours * 3600;
return val;
}
function getDateFromTW(str, isTimeOnly) {
//13.02.11 17:51:31
var timeParts, seconds;
if (isTimeOnly) {
timeParts = str.split(":");
seconds = timeParts[2];
var val = new Date();
val.setHours(timeParts[0]);
val.setMinutes(timeParts[1]);
if (seconds.length > 2) {
var temp = seconds.split(".");
val.setSeconds(temp[0]);
val.setMilliseconds(temp[1]);
} else {
val.setSeconds(seconds);
}
return val;
} else {
var parts = str.split(" ");
var dateParts = parts[0].split(".");
timeParts = parts[1].split(":");
seconds = timeParts[2];
var millis = 0;
if (seconds.length > 2) {
var temp = seconds.split(".");
seconds = temp[0];
millis = temp[1];
} if (dateParts[2].length == 2) {
dateParts[2] = (new Date().getFullYear() + '').substr(0, 2) + dateParts[2];
}
return new Date(dateParts[2], (dateParts[1] - 1), dateParts[0], timeParts[0], timeParts[1], seconds, millis);
}
}
function getDateFromTodayTomorrowTW(str) {
var currentT = new Date();
var dateParts = [];
var parts = $.trim(str).split(" ");
if (str.indexOf(trans.tw.all.tomorrow) != -1) {
// morgen om 06:35:29 uur
dateParts[0] = currentT.getDate() + 1;
dateParts[1] = currentT.getMonth();
} else if (str.indexOf(trans.tw.all.today) != -1) {
// vandaag om 02:41:40 uur
dateParts[0] = currentT.getDate();
dateParts[1] = currentT.getMonth();
} else {
// op 19.05. om 11:31:51 uur
dateParts = parts[1].split(".");
dateParts[1] = parseInt(dateParts[1], 10) - 1;
}
// last part is "hour" but there is a script from lekensteyn that
// corrects the projected arrival time each second
// the script has not been updated to add the word "hour" after the time.
var timeParts = parts[parts.length - 2].split(":");
if (timeParts.length === 1) {
timeParts = parts[parts.length - 1].split(":");
}
var seconds = timeParts[2];
var millis = 0;
if (seconds.length > 2) {
var temp = seconds.split(".");
seconds = temp[0];
millis = temp[1];
}
return new Date(new Date().getFullYear(), dateParts[1], dateParts[0], timeParts[0], timeParts[1], seconds, millis);
}
function isDateInNightBonus(date) {
if (!world_config.nightbonus.active) return false;
return date.getHours() >= world_config.nightbonus.from && date.getHours() < world_config.nightbonus.till;
}
function getBuildingSpace() {
var total = 0;
for (var building = 0; building < world_data.buildingsSize.length; building++) {
var b = world_data.buildingsSize[building];
if (parseInt(game_data.village.buildings[b[0]], 10) > 0) {
total += b[1][parseInt(game_data.village.buildings[b[0]], 10) - 1];
}
}
return total;
}
function getBuildingPoints() {
var total = 0;
for (var building = 0; building < world_data.buildingsPoints.length; building++) {
var b = world_data.buildingsPoints[building];
if (parseInt(game_data.village.buildings[b[0]], 10) > 0) {
total += b[1][parseInt(game_data.village.buildings[b[0]], 10) - 1];
}
}
return total;
}
if (user_data.jumper.enabled) {
(function() {
//console.time("jumper");
try {
var cell = "";
cell += "";
cell += "";
cell += " ";
$("#menu_row2").append("
" + cell + "
");
$("#sangujumper").keyup(function (e) {
if (e.which == 13) {
$("#sangujumperOpen").click();
}
});
$("#sangujumperOpen").click(function () {
var input = $("#sangujumper");
if ($("#sanguJumperFrame").is(":visible")) {
var village = getVillageFromCoords(input.val(), true);
if (village.isValid) {
trackClickEvent("JumperOpen_RealValue");
// Jump to coordinates on the map
location.href = getUrlString("&screen=map&x=" + village.x + "&y=" + village.y);
} else {
// incorrect coordinates
if (!$("#sangujumperpos").is(":visible")) {
$("#sangujumperpos").show();
input.css("border", "1px solid red");
} else
$("#sangujumperpos").hide();
}
} else {
// activate mapJumper
var input = $("#sangujumper");
if (input.val() == "") {
$("#sanguJumperFrame").fadeIn();
} else {
$("#sanguJumperFrame").show();
$("#sangujumperOpen").click();
}
}
return false;
});
if (user_data.jumper.autoShowInputbox) {
$("#sangujumperOpen").click();
}
} catch (e) { handleException(e, "jumper"); }
//console.timeEnd("jumper");
}());
}
// Send usage statistics to GA once/day
(function() {
//console.time("ga");
try {
var loginMonitor = pers.get("sanguLogin");
if (loginMonitor !== '') {
var parts = loginMonitor.match(/(\d+)/g);
loginMonitor = new Date(parts[0], parts[1]-1, parts[2]);
//if (Math.abs(loginMonitor.getTime() - (new Date()).getTime()) > 1000 * 3600 * 24) {
if (parseInt(parts[2], 10) != (new Date()).getDate()) {
loginMonitor = '';
}
}
if (loginMonitor === '') {
loginMonitor = new Date();
loginMonitor.setHours(0, 0, 0);
loginMonitor = loginMonitor.getFullYear() + '-' + pad(loginMonitor.getMonth()+1, 2) + '-' + pad(loginMonitor.getDate(), 2);
trackEvent("ScriptUsage", "DailyUsage", loginMonitor);
pers.set("sanguLogin", loginMonitor);
// also log world/tribe usage
trackEvent("ScriptUsage", "WorldUsage", game_data.world);
trackEvent("ScriptUsage", "TribeUsage", game_data.world + " " + game_data.player.ally_id);
trackEvent("ScriptUsage", "HasPremium", game_data.player.premium ? "Yes" : "No"); // Do we need to support non PA users?
trackEvent("ScriptUsage", "HasAM", game_data.player.account_manager ? "Yes" : "No"); // Do we need to do stuff on the AM pages?
}
} catch (e) { handleException(e, "ga"); }
//console.timeEnd("ga");
}());
q("-------------------------------------------------------------------- Start: "+sangu_version);
// BEGIN PAGE PROCESSING
switch (current_page.screen) {
case "overview":
// MAIN VILLAGE OVERVIEW
(function() {
/**
* The slowest unit in the village in the form unit_spear
*/
var slowest_unit = null;
(function() {
/**
* Each key is in the form unit_sword and holds the total amount of the type in the village
* (own and supporting troops combined)
*/
var totalUnits = {},
/**
* Total population of all units (own + supporting)
*/
totalFarm = 0,
/**
* Own population only (total - supporting)
*/
ownFarmTotal = 0,
/**
* jQuery div with the troops in the village (becomes the own troops only div)
*/
unitTable = $("#show_units"),
/**
* HTML table builder string for the supporting troops div
*/
supportingTroopsTable = "
";
try {
$("#show_units > h4").prepend(trans.sp.main.unitsReplacement);
// calculate current stack
$("table:first td", unitTable).not(":last").each(function () {
var unit = $('img', this)[0].src,
unitsSize,
unitAmount;
unit = unit.substr(unit.lastIndexOf('/') + 1);
unit = unit.substr(0, unit.lastIndexOf('.'));
unitsSize = world_data.unitsSize[unit];
unitAmount = $('strong', this);
unitAmount[0].id = "spAmount" + unit;
unitAmount = unitAmount[0].innerHTML;
if( unit.match("knight") && !unitAmount ) {
unitAmount = 1;
}
totalUnits[unit] = unitAmount;
totalFarm += unitsSize * unitAmount;
if (slowest_unit == null || world_data.unitsSpeed[slowest_unit] < world_data.unitsSpeed[unit]) {
slowest_unit = unit;
}
});
// fetch own troops
if (user_data.overview.ajaxSeperateSupport && totalFarm > 0) {
if (server_settings.ajaxAllowed) {
ajax("place",
function (placeText) {
if (placeText.find(".unitsInput").size() > 0) {
slowest_unit = null;
placeText.find(".unitsInput").each(function () {
// separate own / supporting troops
var unit = 'unit_' + this.id.substr(this.id.lastIndexOf("_") + 1);
var unitAmount = $(this).next().text().substr(1);
unitAmount = parseInt(unitAmount.substr(0, unitAmount.length - 1), 10);
var unitsSize = world_data.unitsSize[unit];
ownFarmTotal += unitsSize * unitAmount;
var unitLabel = $("#spAmount" + unit);
var supportingTroopsAmount = totalUnits[unit] - unitAmount;
if (supportingTroopsAmount > 0) {
var unitDesc = $.trim(unitLabel.parent().text());
unitDesc = unitDesc.substr(unitDesc.indexOf(" ") + 1);
supportingTroopsTable +=
"
";
incomingTable.find("tbody:first").prepend(dodgeMenu);
// checkbox manipulation
$("#uncheckSupport").click(function () {
$("input.incSupport", incomingTable).prop("checked", false);
});
$("#checkSupport").click(function () {
$("input.incSupport", incomingTable).prop("checked", true);
});
var buttonParent = $("#commandInput").parent();
var commandIdToCoordCache = []; // No Ajax call on multiple renames with {xy}
function renameCommand(commandName) {
var dodgeCell; // capture last cell for dodgeCell coloring
function getCommandIdFromDodgeCell(dodgeCell) {
return Number(dodgeCell.find("span.quickedit").first().attr("data-id"));
}
function getVillageCoordsFromCommandId(commandId, callback) {
if (server_settings.ajaxAllowed) {
if (commandIdToCoordCache[commandId]) {
callback(commandIdToCoordCache[commandId]);
} else {
ajax('screen=info_command&type=other&id='+commandId, function (overview) {
var originVillageLink = $(".village_anchor:first", overview).find("a[href]"),
originVillageDesc = originVillageLink.html(),
originVillage = getVillageFromCoords(originVillageDesc);
commandIdToCoordCache[commandId] = originVillage.coord;
callback(originVillage.coord);
});
}
}
callback('');
}
function executeRename(dodgeCell, commandName) {
function keepTwIcon(dodgeCell, commandName) {
var oldName = $(".quickedit-label", dodgeCell).text().toUpperCase(),
newName = commandName,
i,
unitName;
for (i = 0; i < user_data.mainTagger2.reservedWords.length; i++) {
unitName = user_data.mainTagger2.reservedWords[i];
if (oldName.indexOf(unitName.toUpperCase()) !== -1) {
newName = unitName + ' ' + newName;
return newName; // Only one icon possible
}
}
return newName;
}
var button = dodgeCell.find("input[type='button']"),
newName = user_data.mainTagger2.keepReservedWords ? keepTwIcon(dodgeCell, commandName) : commandName;
button.prev().val(newName);
button.click();
}
$("input.taggerCheckbox", incomingTable).each(function () {
var openRenameButton;
if ($(this).is(":checked")) {
dodgeCell = $(this).parent().next();
openRenameButton = $("a.rename-icon", dodgeCell);
if (openRenameButton.is(":visible")) {
openRenameButton.click();
}
if (commandName.indexOf("{xy}") !== -1) {
getVillageCoordsFromCommandId(getCommandIdFromDodgeCell(dodgeCell), function(vilCoords) {
var nameWithCoords = commandName.replace("{xy}", vilCoords);
setTimeout(executeRename(dodgeCell, nameWithCoords),150);
});
} else {
setTimeout(executeRename(dodgeCell, commandName),150);
}
}
});
if (dodgeCell != null) {
var unitSpeed = $("#slowestUnitCell img").attr("slowestunit");
if (unitSpeed != undefined) {
dodgeCell = dodgeCell.parent().find("td").last().prev();
pers.setCookie("sanguDodge" + getQueryStringParam("village"), unitSpeed + "~" + dodgeCell.text(), user_data.mainTagger2.minutesDisplayDodgeTimeOnMap);
$(".dodgers", incomingTable).css("background-color", "").attr("title", "");
dodgeCell.css("background-color", user_data.colors.good).attr("title", trans.sp.tagger.activeDodgeTime);
}
}
}
// std tag button
var button = $("");
button.click(function () {
trackClickEvent("MainTagger-CustomRename");
var tagName = $("#commandInput").val();
renameCommand(tagName);
});
buttonParent.append(button);
if (user_data.mainTagger2.otherDescs != null && user_data.mainTagger2.otherDescs != false) {
$.ctrl = function(key, callback, args) {
$(document).keydown(function(e) {
if(!args) args=[]; // IE barks when args is null
if(e.keyCode == key.charCodeAt(0) && e.ctrlKey) {
e.preventDefault();
e.stopPropagation();
callback.apply(this, args);
return false;
}
});
};
// custom buttons
$.each(user_data.mainTagger2.otherDescs, function (index, val) {
if (val.active) {
var button = $("").click(
function () {
// Cannot use input:checked : this works for Firefox but there is a bug in Opera
trackClickEvent("MainTagger-ConfigRename");
renameCommand($(this).attr("data-rename-to"));
});
buttonParent.append(button);
}
$.ctrl(val.hitKey, function(s) {
trackClickEvent("MainTagger-ConfigRename");
renameCommand(val.renameTo);
});
});
}
// add checkboxes
var lastRowIndex = rows.size(),
lastSend = 0,
prevSendTime = 0,
firstNight = true,
amountOfAttacks = 0;
rows.each(function (rowIndex, rowValue) {
var row = $(rowValue);
if (rowIndex == 0) {
// headerrow
var header = "
";
header += "";
header += "
";
row.replaceWith("
" + header + "
" + trans.sp.tagger.incomingTroops + "
" + trans.sp.tagger.arrival + "
" + trans.sp.tagger.arrival + "
" + trans.sp.tagger.dodgeTime + "
" + "
");
$("#checkAll").click(function () {
$("input.incAt", incomingTable).prop("checked", true);
});
$("#uncheckAll").click( function () {
$("input.incAt", incomingTable).prop("checked", false);
});
} else {
// non header row types
if (row.find("th").size() != 0) {
// this part is only executed when attacks can be ignored
// select all checkbox row (right above link rows)
$("th:first", row).replaceWith("
" + $("th:first", row).text() + "
");
$("#selectAllIgnore").click(function () {
var ingoreBoxes = $("input[name^='id_']", incomingTable);
var isChecked = $("#selectAllIgnore").is(":checked");
ingoreBoxes.each(function() {
$(this).attr("checked", isChecked);
});
});
row.prepend("
# " + amountOfAttacks + "
").find("td:last").attr("colspan", 4);
} else if (row.find("td").size() == 1) {
// link-rows (bottom)
if ($("#switchModus").size() == 0) {
if ($("#selectAllIgnore").size() == 0) {
// attack hiding disabled in tw settings -> there is not yet a totalrow
row.prepend("
');
}
//alert("Hoi");
}, {});
}*/
// When sending os, calculate how much population in total is sent
if (isSupport) {
var totalPop = 0;
$.each(world_data.units, function (i, val) {
var amount = unitsSent[val];
if (amount != 0) {
totalPop += amount * world_data.unitsPositionSize[i];
}
});
var unitTable = $("table.vis:last", content_value);
unitTable.find("tr:first").append('
');
unitTable.find("tr:last").append('
' + formatNumber(totalPop) + '
');
}
} catch (e) { handleException(e, "info_command-command"); }
//console.timeEnd("info_command-command");
}());
}
break;
case "market":
(function() {
try {
if (location.href.indexOf('try=confirm_send') > -1) {
if (user_data.proStyle && user_data.market.autoFocus) {
$("input[type='submit']").focus();
}
}
else if (location.href.indexOf('&mode=') == -1 || location.href.indexOf('&mode=send') > -1) {
if (location.href.indexOf('try=confirm_send') == -1) {
// Spice up market:
// 120 x 106 pixels: There are market images that are smaller
// Making all images equally large results in the OK button remaining on the same place
if (user_data.proStyle && user_data.market.resizeImage) {
$("img[src*='big_buildings/market']").width(120).height(106);
}
// New last village:
$("input[type='submit']").click(function () {
var village = getVillageFromCoords($("#inputx").val() + "|" + $("#inputy").val());
if (village.isValid) {
pers.set("lastVil", village.coord);
}
});
// Add last & target
var vilHome = getVillageFromCoords(game_data.village.coord);
var targetLocation = $("#inputx").parent().parent().parent();
var cookie = pers.get("lastVil");
var coord = getVillageFromCoords(cookie);
var htmlStr = '';
if (coord.isValid) {
var dist = getDistance(coord.x, vilHome.x, coord.y, vilHome.y, 'merchant');
htmlStr = printCoord(coord, "» " + trans.sp.all.last + ": " + coord.x + "|" + coord.y);
htmlStr += " " + dist.html + "";
}
// Add target village
var target = getVillageFromCoords(spTargetVillageCookie());
if (target.isValid) {
var dist = getDistance(target.x, vilHome.x, target.y, vilHome.y, 'merchant');
if (htmlStr.length > 0) {
htmlStr += " ";
}
htmlStr += printCoord(target, "» " + trans.sp.all.target + ": " + target.x + "|" + target.y) + " " + dist.html + "";
}
if (htmlStr.length > 0) {
targetLocation.append("
" + htmlStr + "
");
}
// Calculate total resources sent
var table = $("table.vis:last");
if (table.prev().text() == trans.tw.market.incomingTransports) {
var sent = { stone: 0, wood: 0, iron: 0 };
table.find("tr:gt(0)").each(function () {
var cell = $(this).find("td:eq(1)");
var resources = $.trim(cell.text().replace(/\./g, "").replace(/\s+/g, " ")).split(" ");
for (var i = 0; i < resources.length; i++) {
if (resources[i]) {
var restype = cell.find("span.icon:eq(" + i + ")");
for (var resIndex = 0; resIndex < world_data.resources_en.length; resIndex++) {
if (restype.hasClass(world_data.resources_en[resIndex])) {
sent[world_data.resources_en[resIndex]] += parseInt(resources[i], 10);
}
}
}
}
});
table.append("
"
+ "");
/**
* Creates the textarea (and other UI elements) for displaying the user_data
* @param activate {string} either Import or Export
* @param deactivate {string} either Export or Import
* @param textAreaValue {string} the content to assign to the textarea
*/
function importExportHandler(activate, deactivate, textAreaValue) {
var settingsContainer = $("#sanguSettingsTextArea");
$("#sangu"+activate+"Row").addClass("row_b");
$("#sangu"+deactivate+"Row").hide();
$("#sanguSettingsForm").hide();
if (settingsContainer.length === 0) {
//sanguConfigTitle is the h3 defined in inject.js
$("#sanguConfigTitle").parent().append(
trans.sp.sp.settings.importSettingsDesc + "
"
+ "");
} else {
if (textAreaValue.length > 0) {
settingsContainer.val(textAreaValue);
}
}
}
$("#sanguExport").click(function() {
importExportHandler("Export", "Import", JSON.stringify(user_data, null, 4));
$("#sanguSettingsTextArea").select();
return false;
});
$("#sanguImport").click(function() {
var importSanguSettings = $("#importSanguSettings");
importExportHandler("Import", "Export", "");
if (importSanguSettings.length === 0) {
$("#sanguSettingsTextArea")
.after(" ");
$("#importSanguSettings").click(function() {
var overwriteCurrentSettings = true,
newUserData;
try {
newUserData = JSON.parse($("#sanguSettingsTextArea").val());
if (typeof newUserData.proStyle === 'undefined') {
overwriteCurrentSettings = confirm(trans.sp.sp.settings.importError + trans.sp.sp.settings.importErrorContinueAnyway);
}
if (overwriteCurrentSettings) {
user_data = $.extend({}, user_data, newUserData);
pers.set("sangusettings", JSON.stringify(user_data));
alert(trans.sp.sp.settings.importSettingsSuccess);
}
} catch (ex) {
alert(trans.sp.sp.settings.importError + "\n" + ex.message);
}
});
}
$("#sanguSettingsTextArea").focus();
return false;
});
})();
switch (current_page.mode) {
case "vacation":
var maxSitDays = server_settings.maxSitDays;
var daysTable = $("table.vis:eq(1)", content_value);
var days = $("td:last", daysTable).text();
days = maxSitDays - parseInt(days.substr(0, days.indexOf(" ")), 10);
if (days > 0) {
var tillTime = new Date();
tillTime.setDate(tillTime.getDate() + days);
daysTable.append("
");
} else {
daysTable.find("td:last").css("background-color", user_data.colors.error);
}
break;
case "quickbar_edit":
if (user_data.scriptbar.editBoxCols != null && user_data.scriptbar.editBoxCols != false) {
// TODO: textareaIfy: This might be useful on other places aswell. Move to func/UI?
function textareaIfy(element) {
var textarea =
$('