Advertisement
Guest User

Untitled

a guest
Mar 29th, 2017
203
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. "use strict";
  2. /*
  3. TODO/FEATURELIST
  4. -(DONE)auto turnoff after x minutes (settings)
  5. -(DONE)randomize polling time (settings)
  6. -(DONE) fix too many spaces between coords
  7. -(DONE)differentiate between returning attacks and outgoing attacks (for polling)
  8. -(DONE)Prepare script to load remotely
  9. -(DONE)implement settings panel
  10. -(WONTFIX) button to prevent autostop (implemented via settings panel)
  11. -(DONE) settings for template to ignore scouts if not enough
  12. -Village shaper
  13. -stop when having incomings
  14. -(DONE)automatically load world settings
  15. -refresh hiddenframe now button
  16. -(DONE)read reports and base attacks off that
  17.     -also from farm assistant
  18.     -(DONE)options: timeBeforeIgnoringReport,
  19.     -(DONE)a quick way to skip already loaded reports (maybe check it in )
  20.     -automatically update when a new report comes in (see updateCurrentlyAttacking)
  21. -use flags for haul calc in report farming
  22. -settings for max wall level before ignoring
  23. -settings for max useless troops to prevent losses
  24. -disable linked settings if not checked (e.g. disable discount value if discount == false)
  25. -add minpoll to settings
  26. -add delay when loading page (e.g. .4 s)
  27.  
  28. BUGS
  29. - sometimes sends double attacks (e.g. 428|572, 434|567) (maybe at start of attack?, it seems to skip previous)
  30. - (FIXED)ignoring a village sometimes sends just one scout
  31.     - (FIXED)ignoreVillage does not work at all, it just sends whatever we filled in
  32. -
  33.  
  34.  
  35. */
  36. var twf = {
  37.     init: function() {
  38.         twf.helper.init();
  39.         twf.data.init();
  40.         twf.reports.init();
  41.         twf.attacks.init();
  42.         twf.helper.log("All systems ready.", twf.helper.MESSAGE_SUCCES);
  43.         $('#buttons').children().prop('disabled', false).removeClass('btn-disabled');
  44.         if (twf.data.settings.reportFarmer) {
  45.             twf.attacks.attackButton.prop('disabled', true).addClass('btn-disabled');
  46.             twf.helper.log("You have enabled the Report Farmer. Please press the \"Read reports\"-button to continue, or disable the Report Farmer.", twf.helper.MESSAGE_WARNING);
  47.         }
  48.     },
  49.     /*
  50.     We have two different attacks: general template based and report based
  51.  
  52.     For report based we do the following: scrape all reports (<X hours ago) @ init
  53.     For each report keep all information in some object (order it by distance to currentvillage, at init, and just start attacking if possible)
  54.     LocalStorage store them with their coords as id: save the following: wall, resource pits, storage, hiding place, lastReportAttack/ScoutTime, barb
  55.     (From hiding and storage you can calculate max lootable stuff)
  56.  
  57.     Whenever an attack lands, we refresh the messages and load the next one.
  58.     If there are no scouts available for the attack we assume 0 res left (maybe change this to some heuristic (e.g.) expected gs - haul?)
  59.    
  60.     (maybe keep track of what villages we are already attacking, for when we use multiple farms or when we reorder their stuff based on
  61.     some heuristic)
  62.     Then we order the villages on efficiency, taking into account function expectedRes(current, mines, storage, timeSinceScout, traveltime)
  63.     and time efficiency (e.g. res/hour)
  64.     we discount hard over time since scout, so we waste little troops for unsuccesfull hauls. We also sent a minimum amount of lights, based on wall
  65.     This has to be taken into account too, cause wasting many lights on little farm but high wall is shit
  66.  
  67.  
  68.     reports: {
  69.         "123|654": {
  70.             buildings: {
  71.                 wall: 0,
  72.                 iron: 5,
  73.                 clay: 5,
  74.                 wood: 5,
  75.                 warehouse: 10,
  76.                 hiding: 3,
  77.             },
  78.             isBarb: true,
  79.             lastAttack: Date(xx),
  80.             lastScout: Date(xx),
  81.             lostTroops: true,
  82.             currentlyUnderAttack: false (//update in attack module, then set false when receiving report),
  83.             resLeft: 0
  84.         }
  85.     }
  86.     functions
  87.    
  88.     loadOldReportData()
  89.     readReport()
  90.     saveReport()
  91.     expectedRes(resLastReport, MineLevels, StorageLevel, HidingLevel, timeSinceLastReport, travelTime/coords)
  92.     calcDistance()
  93.     orderVillages(someHeuristic, where currentlyUnderAttack discounts very hard e.g. /1000)
  94.     onFrameLoaded();
  95.     getReportsOnPage();
  96.  
  97.     */
  98.  
  99.     // we only start automatic reading from the moment startattack is ran
  100.     // if we have not parsed reports beforehand, error and ask for parsing
  101.     reports: {
  102.         firstRunFinished: false,
  103.         currentlyReading: false, //flag to check if it is reading right now.
  104.         allowAutomaticReading: false,
  105.         reportFrame: null, //contains the frame
  106.         lastRun: null, //start time of previous run
  107.         thisRun: null, // start time of current run
  108.         currentPage: 1, // current page of reportList (1-indexed)
  109.         data: {},
  110.         reportsToRead: [],
  111.         init: function() {
  112.             $("#start_reports").click(twf.reports.startReading);
  113.             $("#stop_reports").click(twf.reports.stopReading);
  114.             console.log(this.lastRun);
  115.  
  116.             twf.reports.loadOldReportData();
  117.  
  118.             twf.reports.reportParserUrl = "/game.php?village=" + game_data.village.id + "&screen=report&mode=attack&group_id=-1&view=";
  119.             twf.reports.reportListUrl = "/game.php?village=" + game_data.village.id + "&screen=report&mode=attack&group_id=-1&from=";
  120.  
  121.             twf.reports.reportFrame = twf.helper.createHiddenFrame(twf.reports.reportListUrl + "0", twf.reports.onFrameLoaded, "report_parser_hidden_frame");
  122.  
  123.             this.lastRun = new Date(twf.data.loadWorldLevel('reports_lastRun'));
  124.             twf.helper.log("Report farmer module ready.", twf.helper.MESSAGE_SUCCES);
  125.         },
  126.         startReading: function() {
  127.             // do nothing if we are busy
  128.             if (twf.reports.currentlyReading) {
  129.                 twf.helper.log("Already reading. Not starting over.", twf.helper.MESSAGE_WARNING);
  130.                 return;
  131.             }
  132.             // this is a manual override, so remove timer
  133.             if (twf.data.timers.reportPollTimer) {
  134.                 clearTimeout(twf.data.timers.reportPollTimer);
  135.                 twf.data.timers.reportPollTimer = null;
  136.                 console.debug("Removed reportPollTimer from within startReading");
  137.             }
  138.  
  139.             // handle buttons
  140.             $("#start_reports").hide();
  141.             $("#stop_reports").show();
  142.  
  143.             // reset vars
  144.             twf.reports.currentlyReading = true;
  145.             twf.reports.currentPage = 1;
  146.             twf.reportsToRead = [];
  147.  
  148.             twf.reports.thisRun = twf.helper.getServerTime(twf.reports.reportFrame);
  149.  
  150.             // reload the page so onLoad fires
  151.             twf.helper.spinner.show();
  152.             twf.reports.reportFrame.attr('src', twf.reports.reportListUrl + "0");
  153.         },
  154.         stopReading: function() {
  155.             // handle buttons
  156.             $("#start_reports").show();
  157.             $("#stop_reports").hide();
  158.  
  159.             // turn off currently reading
  160.             twf.reports.currentlyReading = false;
  161.  
  162.             // check if we should enable the attacks
  163.             if (twf.reports.firstRunFinished) {
  164.                 twf.attacks.attackButton.removeClass("btn-disabled").prop("disabled", false);
  165.             }
  166.  
  167.             // update lastRun and store it if we finished the run
  168.             if (twf.reports.thisRun && twf.reports.firstRunFinished) {
  169.                 twf.reports.lastRun = twf.reports.thisRun;
  170.                 twf.data.storeWorldLevel('reports_lastRun', twf.reports.lastRun);
  171.             }
  172.  
  173.             twf.reports.updateReportStats(); // set it to "Done."
  174.         },
  175.         onFrameLoaded: function() {
  176.             try {
  177.                 twf.helper.spinner.fadeOut();
  178.                 twf.helper.checkBotProtection();
  179.                 twf.reports.updateReportStats(false, false);
  180.  
  181.                 if (twf.reports.currentlyReading) {
  182.                     if (twf.reports.reportFrame[0].contentWindow.location.search.indexOf('view') == -1) {
  183.                         // we are in the list
  184.                         let maxPage = twf.reports.getMaxPage();
  185.                         if (twf.reports.currentPage <= maxPage) {
  186.                             // still pages left, update stats and interpret
  187.                             twf.reports.updateReportStats(true, maxPage);
  188.                             twf.reports.handleList();
  189.                         } else if (twf.reports.reportsToRead.length > 0) {
  190.                             // we are on the last page and have reports to read!
  191.                             twf.helper.spinner.show();
  192.                             twf.helper.log("Finished loading readable reports...", twf.helper.MESSAGE_SUCCES);
  193.                             console.debug("Last page of reportlist -> going to view a report!");
  194.                             twf.reports.reportFrame.attr('src', twf.reports.reportParserUrl + twf.reports.reportsToRead.shift());
  195.                         } else {
  196.                             // last page and not a single report found! (could also be no reports at all)
  197.                             twf.helper.log("Not a single useful report found. Suspicious...", twf.helper.MESSAGE_WARNING);
  198.                             twf.reports.firstRunFinished = true;
  199.                             twf.reports.stopReading();
  200.                         }
  201.  
  202.                     } else {
  203.                         // we are in a report
  204.                         twf.reports.handleReport();
  205.                     }
  206.                 }
  207.             } catch (error) {
  208.                 twf.helper.stopEverything();
  209.                 console.error(error);
  210.                 alert("BOT PROTECTION? " + error);
  211.             }
  212.  
  213.         },
  214.         parseAndStore: function() {
  215.             // TODO CHECK IF IT IS IN STORAGE YET
  216.             let coords = twf.reports.reportFrame.contents().find('span.quickedit-label').text().trim().match(/\d{1,3}\|\d{1,3}/g);
  217.             coords = coords[coords.length - 1];
  218.  
  219.             // arrival time
  220.             let arrival = twf.reports.reportFrame.contents().find('.small.grey').parent().text().trim().split(" ");
  221.             arrival[0] = arrival[0].split(".").reverse().join("-"); //fixes date
  222.             arrival[1] = arrival[1].replace(/:([^:]*)$/, "." + '$1'); //fixes time (replace last : with .)
  223.             arrival = new Date("20" + arrival[0] + "T" + arrival[1] + "Z"); // sets the date
  224.  
  225.             if (twf.reports.data[coords] && twf.reports.data[coords].scoutBuilding && arrival <= twf.reports.data[coords].scoutBuilding) {
  226.                 // if we have already scouted the buildings here and this report is older than our most recent building reports
  227.                 // we have nothing to gain from it, so we can ignore it
  228.                 console.debug("Skipping " + coords + " because we have fresher building data. Current: " + arrival.toUTCString() + ", in db: " + twf.reports.data[coords].scoutBuilding.toUTCString());
  229.                 return;
  230.             }
  231.  
  232.             // luck
  233.             let luck = parseFloat(twf.reports.reportFrame.contents().find('.nobg b').text().trim().replace("%", ""));
  234.  
  235.             // send from
  236.             let sentFromId = parseInt(twf.reports.reportFrame.contents().find('[data-id].village_anchor').eq(0).attr('data-id'));
  237.             let sentFromCoords = twf.reports.reportFrame.contents().find('[data-id].village_anchor').eq(0).text().trim().match(/\d{1,3}\|\d{1,3}/g);
  238.             sentFromCoords = sentFromCoords[sentFromCoords.length - 1];
  239.  
  240.             // units send and lost
  241.             let unitsSent = {};
  242.             let unitsDied = {};
  243.             for (let i in twf.data.unitTypes) { // i is a string, wtf
  244.                 unitsSent[twf.data.unitTypes[i]] = parseInt(twf.reports.reportFrame.contents().find('#attack_info_att_units .unit-item').eq(i).text().trim());
  245.                 unitsDied[twf.data.unitTypes[i]] = parseInt(twf.reports.reportFrame.contents().find('#attack_info_att_units .unit-item').eq(twf.data.unitTypes.length + parseInt(i)).text().trim());
  246.             }
  247.  
  248.             // is player
  249.             let isPlayer = twf.reports.reportFrame.contents().find('#attack_info_def th').eq(1).text().trim() != "---";
  250.  
  251.             let enemyHome = null;
  252.             let enemyDied = null;
  253.  
  254.             // if atleast on troop survived, we have data on their troops
  255.             for (let i in unitsSent) {
  256.                 if (unitsSent[i] != unitsDied[i]) {
  257.                     // we have at least one surviving troop
  258.                     // instantiate objects
  259.                     enemyHome = {};
  260.                     enemyDied = {};
  261.  
  262.                     // parse their units
  263.                     // this ignores militia
  264.                     for (let i in twf.data.unitTypes) { // i is a string, wtf
  265.                         enemyHome[twf.data.unitTypes[i]] = parseInt(twf.reports.reportFrame.contents().find('#attack_info_def_units .unit-item').eq(i).text().trim());
  266.                         // note the length + 1 (because we ignore the mob)
  267.                         enemyDied[twf.data.unitTypes[i]] = parseInt(twf.reports.reportFrame.contents().find('#attack_info_def_units .unit-item').eq(twf.data.unitTypes.length + 1 + parseInt(i)).text().trim());
  268.                     }
  269.                     break;
  270.                 }
  271.             }
  272.  
  273.             // results
  274.             let resTaken = twf.reports.reportFrame.contents().find('#attack_results tr td').eq(1).text().trim().split("/");
  275.             let maxResTaken = parseInt(resTaken[1]);
  276.             resTaken = parseInt(resTaken[0]);
  277.             // keep track of what level we scouted
  278.             let scoutLevel = 0;
  279.             // res left (if no data == null)
  280.             let resLeft = null;
  281.             // buildings scouted (no data -> null)
  282.             let buildingData = null;
  283.             // troops outside village
  284.             let enemyAway = null;
  285.             // scout info
  286.             if (twf.reports.reportFrame.contents().find("#attack_spy_resources").length > 0) {
  287.                 // we have scouted resources
  288.                 scoutLevel = 1;
  289.                 let resArray = twf.reports.reportFrame.contents().find('#attack_spy_resources .nowrap').text().trim().split(" ");
  290.                 for (let i in resArray) {
  291.                     let p = parseInt(resArray[i]);
  292.                     if (!isNaN(p)) {
  293.                         resLeft += p;
  294.                     }
  295.                 }
  296.             } else if (resTaken < maxResTaken) {
  297.                 // if we didnt have full haul we can assume no res left
  298.                 resLeft = 0;
  299.             } else {
  300.                 // if we did have full haul we have no info, so signify with -1
  301.                 resLeft = -1;
  302.             }
  303.             if (twf.reports.reportFrame.contents().find('#attack_spy_building_data').length > 0) {
  304.                 // we have scouted buildings
  305.                 scoutLevel = 2;
  306.                 // init buildingData
  307.                 buildingData = {};
  308.                 // parse them to array
  309.                 let parsedData = JSON.parse(twf.reports.reportFrame.contents().find('#attack_spy_building_data').val());
  310.                 // parse them in a new array
  311.                 for (let i in parsedData) {
  312.                     // for all data that we have, store them in buildingdata as building:level
  313.                     buildingData[parsedData[i].id] = parseInt(parsedData[i].level);
  314.                 }
  315.                 // then run over all buildings and set them to 0 if they don't exist yet
  316.                 for (let i in twf.data.buildingTypes) {
  317.                     if (buildingData[twf.data.buildingTypes[i]] == undefined) {
  318.                         buildingData[twf.data.buildingTypes[i]] = 0;
  319.                     }
  320.                 }
  321.             }
  322.             if (twf.reports.reportFrame.contents().find('#attack_spy_away').length > 0) {
  323.                 // we have data on troops away
  324.                 scoutLevel = 3;
  325.                 // init enemy away
  326.                 enemyAway = {};
  327.                 for (let i in twf.data.unitTypes) { // i is a string, wtf
  328.                     enemyAway[twf.data.unitTypes[i]] = parseInt(twf.reports.reportFrame.contents().find('#attack_spy_away .unit-item').eq(i).text().trim());
  329.                 }
  330.             }
  331.  
  332.             // we have interpreted all there is,
  333.             // now store it
  334.  
  335.             // first retrieve the old report (to overwrite it)
  336.             let report = {};
  337.             if (twf.reports.data[coords]) {
  338.                 report = twf.reports.data[coords];
  339.             }
  340.             report['lastAttack'] = arrival;
  341.             report['luck'] = luck;
  342.             report['sentFromId'] = sentFromId;
  343.             report['sentFromCoords'] = sentFromCoords;
  344.             report['isPlayer'] = isPlayer;
  345.             report['unitsSent'] = unitsSent;
  346.             report['unitsDied'] = unitsDied;
  347.             if (enemyHome) {
  348.                 report['enemyHome'] = enemyHome;
  349.                 report['enemyDied'] = enemyDied;
  350.             }
  351.             // do not save resTaken and maxResTaken because we can infer all we need to know from resLeft
  352.             //report['resTaken'] = resTaken;
  353.             //report['maxResTaken'] = maxResTaken;
  354.             report['resLeft'] = resLeft; //-1 signifies no data
  355.             if (buildingData) {
  356.                 report['buildingData'] = buildingData;
  357.             }
  358.             if (enemyAway) {
  359.                 report['enemyAway'] = enemyAway;
  360.             }
  361.  
  362.             // store when the last scout was
  363.             if (scoutLevel) {
  364.                 if (scoutLevel >= 1) {
  365.                     report['scoutRes'] = arrival;
  366.                 }
  367.                 if (scoutLevel >= 2) {
  368.                     report['scoutBuilding'] = arrival;
  369.                 }
  370.                 if (scoutLevel >= 3) {
  371.                     report['scoutAway'] = arrival;
  372.                 }
  373.             }
  374.  
  375.             // store to data object
  376.             twf.reports.data[coords] = report;
  377.  
  378.             // store in localstorage
  379.             twf.data.storeWorldLevel('reports', twf.reports.data);
  380.             console.debug(report);
  381.  
  382.             // done
  383.         },
  384.         handleReport: function() {
  385.             twf.helper.spinner.fadeOut();
  386.  
  387.             if (twf.reports.reportsToRead.length > 0) {
  388.                 twf.reports.parseAndStore();
  389.                 twf.helper.spinner.show();
  390.                 twf.reports.reportFrame.attr('src', twf.reports.reportParserUrl + twf.reports.reportsToRead.shift());
  391.             } else {
  392.                 twf.helper.log("Done reading reports!", twf.helper.MESSAGE_SUCCES);
  393.                 twf.reports.firstRunFinished = true;
  394.                 twf.reports.stopReading()
  395.             }
  396.  
  397.         },
  398.         getMaxPage: function() {
  399.             let t = twf.reports.reportFrame.contents().find('.paged-nav-item:last');
  400.             if (t) {
  401.                 // found atleast one page
  402.                 t = t.text().trim(); //yields "[xx]"
  403.                 t = parseInt(t.substring(1, t.length - 1));
  404.             } else {
  405.                 // no extra pages found
  406.                 t = 1;
  407.             }
  408.             return t;
  409.         },
  410.         // first argument -> true if still in list, maxPage is the max page
  411.         updateReportStats: function(pages, maxPage) {
  412.             if (!twf.reports.currentlyReading && twf.reports.firstRunFinished) {
  413.                 // not reading and finished
  414.                 $("#reports_left").html("<b>Done!</b>");
  415.             } else if (!twf.reports.currentlyReading) {
  416.                 $("#reports_left").text("Waiting...");
  417.             } else if (pages) {
  418.                 // loading reports
  419.                 $("#reports_left").text("page " + twf.reports.currentPage + "/" + maxPage);
  420.             } else {
  421.                 // reading reports
  422.                 $("#reports_left").text(twf.reports.reportsToRead.length + " reports left");
  423.             }
  424.         },
  425.         handleList: function() {
  426.             let finished = false;
  427.             twf.reports.reportFrame.contents().find('#report_list tr td:nth-of-type(3)').each(function(i, e) {
  428.                 // retrieve some info
  429.                 let coords = $(e).siblings().find('.quickedit-label').text().match(/\d{1,3}\|\d{1,3}/g);
  430.                 coords = coords[coords.length - 1];
  431.  
  432.                 let id = $(e).siblings().find("[data-id]").attr('data-id');
  433.  
  434.                 let receivedAt = twf.reports.getReportTimeInList($(e).text());
  435.                 let now = twf.helper.getServerTime(twf.reports.reportFrame);
  436.                 // now > receivedAt, because receivedAt is rounded down to full minutes (e.g. 18:26:12.123 -> 18:26)
  437.  
  438.                 // we read a report if it is less than 24 hours old AND we do not have a more recent scout
  439.                 // because if we have a more recent scout there is nothing to know
  440.                 // however if we have a more recent attack but not scout we can still receive building info
  441.                 if (now - receivedAt >= 1000 * 60 * 60 * twf.data.settings.reportMaxReadAge) {
  442.                     // older than 24 -> we can immediately stop
  443.                     console.debug("Skipped " + coords + " received at " + receivedAt.toUTCString() + " because > " + twf.data.settings.reportMaxReadAge + " hours.");
  444.                     finished = true;
  445.                     return false; //break out of each-loop
  446.                 } else if (twf.reports.lastRun && twf.reports.lastRun - receivedAt >= 1000 * 60) {
  447.                     // the report was already caught in the last run
  448.                     console.debug("Skipped " + coords + " received at " + receivedAt.toUTCString() + " because we caught it last run. (Start of last run: " + twf.reports.lastRun.toUTCString() + ")");
  449.                     finished = true;
  450.                     return false;
  451.                 } else if (twf.reports.data[coords] && twf.reports.data[coords].scoutBuilding && receivedAt /*+ 60 * 1000*/ <= twf.reports.data[coords].scoutBuilding) {
  452.                     // the way it is now: we ignore if two attacks have landed in the same minute, which should never happen.
  453.                     // the way below adds a lot of reports that are useless
  454.  
  455.                     // the report was received somewhere in the minute that the previous scout report was received
  456.                     // we make it a minute fresher, because receivedAt is rounded down to full minutes, while the other one is exact
  457.                     // not that it matters that much
  458.                     console.debug("Skipped " + coords + " received at " + receivedAt.toUTCString() + " last building scout is more recent (" + twf.reports.data[coords].scoutBuilding.toUTCString() + ")");
  459.                 } else {
  460.                     console.debug("Lastrun: " + (twf.reports.lastRun ? twf.reports.lastRun.toUTCString() : "--no such run--") + ". Received: " + receivedAt.toUTCString());
  461.                     //console.debug("twf.reports.lastRun (" + twf.reports.lastRun + ") && twf.reports.lastRun >= receivedAt + 1000 * 5 == " + (twf.reports.lastRun >= receivedAt + 1000 * 5) + "( receivedAt + 1000 * 5: " + (receivedAt.getTime() + 1000 * 5));
  462.                     //console.debug("Adding " + coords + " received at " + receivedAt.toUTCString() + ". Time diff (h): " + ((now-receivedAt)/1000/60/60));
  463.                     // we have a valid report, so add it
  464.                     twf.reports.reportsToRead.push(id);
  465.                 }
  466.  
  467.             });
  468.             // we have added all reports and maybe we have finished because there are no recent reports left
  469.             // if we have finished, we start interpreting them
  470.             if (finished) {
  471.                 twf.helper.spinner.show();
  472.                 twf.helper.log("Finished loading readable reports...", twf.helper.MESSAGE_SUCCES);
  473.                 twf.reports.reportFrame.attr('src', twf.reports.reportParserUrl + twf.reports.reportsToRead.shift());
  474.             } else { // not finished, so go to the next page
  475.                 twf.helper.spinner.show();
  476.                 twf.reports.currentPage = twf.reports.currentPage + 1;
  477.                 console.debug("Going to next page: " + twf.reports.currentPage);
  478.                 twf.reports.reportFrame.attr('src', twf.reports.reportListUrl + (twf.reports.currentPage - 1) * 12); //12 reports per page, 1-indexed
  479.             }
  480.         },
  481.         getReportTimeInList(text) {
  482.             let dateTime = text.split(" ");
  483.             let dateString = "20" + dateTime[0].split(".").reverse().join("-"); //should yield something like 2018-05-06
  484.             return new Date(dateString + "T" + dateTime[1] + "Z");
  485.         },
  486.         loadOldReportData: function() {
  487.             let tempReports = twf.data.loadWorldLevel('reports');
  488.             if (tempReports) {
  489.                 // parse dates as objects
  490.                 for (let i in tempReports) {
  491.                     if (tempReports[i].scoutRes) {
  492.                         tempReports[i].scoutRes = new Date(tempReports[i].scoutRes);
  493.                     }
  494.                     if (tempReports[i].scoutBuilding) {
  495.                         tempReports[i].scoutBuilding = new Date(tempReports[i].scoutBuilding);
  496.                     }
  497.                     if (tempReports[i].scoutAway) {
  498.                         tempReports[i].scoutAway = new Date(tempReports[i].scoutAway);
  499.                     }
  500.                     if (tempReports[i].lastAttack) {
  501.                         tempReports[i].lastAttack = new Date(tempReports[i].lastAttack);
  502.                     }
  503.  
  504.                 }
  505.                 twf.helper.log("Reports succesfully loaded.", twf.helper.MESSAGE_SUCCES);
  506.                 twf.reports.data = tempReports;
  507.             } else {
  508.                 twf.helper.log("Failed to load reports. Maybe none yet?", twf.helper.MESSAGE_WARNING);
  509.             }
  510.         },
  511.         // assumeRes = value of res we expect there to be. This overwrites whatever is in the report
  512.         // assumeBuildings = level of storage, hide, wood, clay and iron we expect. This overwrites the report if it exists
  513.         // if you do not want to assume anything, make sure it is in the report and pass false (NOT false-y, actual false)
  514.         expectedRes: function(coords, unit, serverTime, assumeRes, assumeBuildings) {
  515.             let lastReport = twf.reports.data[coords];
  516.  
  517.             let distance = twf.helper.getDistance(game_data.village.coord, coords);
  518.             let travelTimeInHours = twf.helper.getTravelTimeInMinutes(distance, unit) / 60;
  519.             let timePassedInHours = (serverTime.getTime() - lastReport.lastAttack.getTime()) / (1000 * 60 * 60);
  520.  
  521.             //console.debug(coords + ". Distance: " + distance + ". Travel time: " + travelTimeInHours + ". Time since attack: " + timePassedInHours);
  522.  
  523.             let maxStorage = null;
  524.             let maxHide = null
  525.             let resOver = null;
  526.             let resPerHour = null;
  527.  
  528.             if (assumeBuildings !== false) {
  529.                 console.debug("Assuming buildings!");
  530.                 maxStorage = twf.helper.getMaxStorage(assumeBuildings.storage);
  531.                 maxHide = twf.helper.getMaxHiding(assumeBuildings.hide);
  532.                 resPerHour = twf.helper.getResPerHour(assumeBuildings.wood) + twf.helper.getResPerHour(assumeBuildings.clay) + twf.helper.getResPerHour(assumeBuildings.iron);
  533.             } else {
  534.                 maxStorage = twf.helper.getMaxStorage(lastReport.buildingData.storage);
  535.                 maxHide = twf.helper.getMaxHiding(lastReport.buildingData.hide);
  536.                 resPerHour = twf.helper.getResPerHour(lastReport.buildingData.wood) + twf.helper.getResPerHour(lastReport.buildingData.clay) + twf.helper.getResPerHour(lastReport.buildingData.iron);
  537.             }
  538.  
  539.             //console.debug("maxStorage: " + maxStorage + ". maxHide: " + maxHide + ". resPerHour: " + resPerHour);
  540.             if (assumeRes !== false) {
  541.                 console.debug("Assuming res left")
  542.                 resOver = assumeRes;
  543.             } else {
  544.                 resOver = lastReport.resLeft;
  545.             }
  546.  
  547.  
  548.             let maxLoot = maxStorage - maxHide;
  549.             let tempRes = Math.min(resOver + (timePassedInHours + travelTimeInHours) * resPerHour, twf.data.settings.reportMaxUseAge * resPerHour);
  550.  
  551.             let expectedRes = Math.min(maxLoot, tempRes)
  552.  
  553.             //console.debug("Expected res for " + coords + "@" + unit + ": " + expectedRes);
  554.  
  555.             if (twf.data.settings.discountTime && twf.data.settings.discountFactor != 1) {
  556.                 expectedRes = expectedRes / (Math.pow(twf.data.settings.discountFactor, timePassedInHours + travelTimeInHours));
  557.             }
  558.  
  559.             //console.debug("Expected discounted res for " + coords + "@" + unit + ": " + expectedRes);
  560.  
  561.             return expectedRes;
  562.  
  563.         },
  564.         lostAllTroops: function(coords) {
  565.             // note that spies do not die (usually)
  566.             // if they have not died, we have all information
  567.             // if they have, we know to send just one
  568.             if (twf.reports.data[coords]) {
  569.                 for (let troop in twf.reports.data[coords].unitsSent) {
  570.                     if (twf.reports.data[coords].unitsSent[troop] != twf.reports.data[coords].unitsDied[troop]) {
  571.                         return false;
  572.                     }
  573.                 }
  574.                 return true;
  575.             } else {
  576.                 return null;
  577.             }
  578.         }
  579.     },
  580.     data: {
  581.         settings: {
  582.             minPollAttack: 0,
  583.             minPollReport: 0,
  584.             //minPoll: 300, // minimum polling time -> prevent detecetion
  585.             //temp
  586.             reportMaxUseAge: 6,
  587.             reportMaxReadAge: 1,
  588.             // end of temp
  589.             resetToFirst: true,
  590.             waitForTroops: true,
  591.             attackPlayers: false,
  592.             pollLate: true,
  593.             pollLateSeconds: 60,
  594.             autoStop: true,
  595.             autoStopMinutes: 120,
  596.             autoStopTimer: 0,
  597.             discountTime: false,
  598.             discountFactor: 1.07, //per hour
  599.             reportFarmer: false
  600.         },
  601.         timers: {
  602.             autoStopTimer: null,
  603.             attackPollTimer: null,
  604.             reportPollTimer: null,
  605.         },
  606.         worldSettings: {
  607.             speed: 1,
  608.             unitSpeed: 1,
  609.             knight: true,
  610.             archer: true,
  611.         },
  612.         travelTime: {
  613.             spear: 18,
  614.             sword: 22,
  615.             axe: 18,
  616.             archer: 18,
  617.             spy: 9,
  618.             light: 10,
  619.             marcher: 10,
  620.             heavy: 11,
  621.             ram: 30,
  622.             catapult: 30,
  623.             snob: 35,
  624.             knight: 10
  625.         },
  626.         carryCapacity: {
  627.             spear: 25,
  628.             sword: 15,
  629.             axe: 10,
  630.             archer: 10,
  631.             spy: 0,
  632.             light: 80,
  633.             marcher: 50,
  634.             heavy: 50,
  635.             ram: 0,
  636.             catapult: 0,
  637.             snob: 0,
  638.             knight: 100
  639.         },
  640.         unitTypes: ["spear", "sword", "axe", "archer", "spy", "light", "marcher", "heavy", "ram", "catapult", "snob", "knight", ],
  641.         buildingTypes: ["main", "hide", "market", "storage", "stable", "smith", "barracks", "place", "wall", "iron", "clay", "wood", "farm", "church", "watchtower", "statue", "garage", "snob"],
  642.         minLightPerWall: [1, 4, 32, 87, 170, 281],
  643.         init: function() {
  644.             this.getAndSaveWorldSettings();
  645.             this.loadSettings();
  646.  
  647.             if (this.settings.autoStop) {
  648.                 twf.data.timers.autoStopTimer = window.setTimeout(twf.attacks.stopAttack, twf.data.settings.autoStopMinutes * 60 * 1000);
  649.                 twf.helper.log("Autostop engaged. Stopping in " + twf.data.settings.autoStopMinutes + " minutes!", twf.helper.MESSAGE_DEFAULT);
  650.             }
  651.  
  652.             if (!this.settings.reportFarmer) {
  653.                 $('td.reports_left').hide();
  654.             }
  655.  
  656.             twf.helper.log("Data module ready.", twf.helper.MESSAGE_SUCCES);
  657.         },
  658.         saveSettings: function() {
  659.             //also validate
  660.             twf.data.settings.resetToFirst = $('#reset_to_first').is(':checked');
  661.             twf.data.settings.waitForTroops = $('#wait_for_troops').is(':checked');
  662.             twf.data.settings.attackPlayers = $('#attack_players').is(':checked');
  663.             twf.data.settings.autoStop = $('#autostop').is(':checked');
  664.             twf.data.settings.pollLate = $('#poll_late').is(':checked');
  665.             twf.data.settings.discountTime = $('#discount_time').is(':checked');
  666.             twf.data.settings.reportFarmer = $('#report_farmer').is(':checked');
  667.  
  668.  
  669.             //values (take first number as value)
  670.             twf.data.settings.autoStopMinutes = parseInt($('#autostop_minutes').val().match(/\d+/)[0]);
  671.             twf.data.settings.pollLateSeconds = parseInt($('#poll_late_seconds').val().match(/\d+/)[0]);
  672.             twf.data.settings.discountFactor = parseFloat($('#discount_factor').val().match(/^[12](\.\d{1,2})*/)[0]); // matches 1, 1.1,1.2, etc
  673.             twf.data.settings.reportMaxReadAge = parseInt($('#report_max_read_age').val().match(/\d+/)[0]);
  674.             twf.data.settings.reportMaxUseAge = parseInt($('#report_max_use_age').val().match(/\d+/)[0]);
  675.  
  676.             // TEMP -> TODO ADD TO CONFIG
  677.             twf.data.settings.minPollAttack = twf.data.settings.minPollAttack;
  678.             twf.data.settings.minPollReport = twf.data.settings.minPollReport;
  679.             // END TEMP
  680.  
  681.             twf.data.storeGlobal('settings', twf.data.settings);
  682.             twf.helper.log("Saved settings!", twf.helper.MESSAGE_SUCCES);
  683.  
  684.             // rerun autoStop
  685.             if (twf.data.settings.autoStop) {
  686.                 window.clearTimeout(twf.data.timers.autoStopTimer);
  687.                 twf.data.timers.autoStopTimer = window.setTimeout(twf.attacks.stopAttack, twf.data.settings.autoStopMinutes * 60 * 1000);
  688.                 twf.helper.log("Autostop engaged. Stopping in " + twf.data.settings.autoStopMinutes + " minutes!", twf.helper.MESSAGE_DEFAULT);
  689.             }
  690.  
  691.             // show reports progress
  692.             if (twf.data.settings.reportFarmer) {
  693.                 twf.reports.loadOldReportData();
  694.                 $('td.reports_left').show();
  695.                 if (twf.reports.firstRunFinished) {
  696.                     twf.attacks.attackButton.removeClass('btn-disabled').prop('disabled', false);
  697.                 } else {
  698.                     twf.attacks.attackButton.addClass('btn-disabled').prop('disabled', true);
  699.                     twf.helper.log("You have enabled the Report Farmer. Please press the \"Read reports\"-button to continue, or disable the Report Farmer.", twf.helper.MESSAGE_WARNING);
  700.                 }
  701.             } else {
  702.                 twf.attacks.attackButton.removeClass('btn-disabled').prop('disabled', false);
  703.                 $('td.reports_left').hide();
  704.             }
  705.  
  706.  
  707.             $('#settings_popup').hide();
  708.         },
  709.         loadSettings: function() {
  710.             let tempSettings = twf.data.loadGlobal('settings');
  711.             // only load if global settings are saved
  712.             if (tempSettings) {
  713.                 twf.data.settings = tempSettings;
  714.                 twf.helper.log("Succesfully retrieved bot settings.", twf.helper.MESSAGE_SUCCES);
  715.             } else {
  716.                 twf.helper.log("Failed to retrieve bot settings. Using defaults.", twf.helper.MESSAGE_ERROR);
  717.             }
  718.  
  719.         },
  720.         getAndSaveWorldSettings: function() {
  721.             let tempWorldSettings = this.loadWorldLevel("worldSettings");
  722.             if (!tempWorldSettings) {
  723.                 let configUrl = '/interface.php?func=get_config';
  724.                 //no world settings -> load and save them
  725.                 $.ajax({
  726.                     url: configUrl,
  727.                 }).fail(function() {
  728.                     twf.helper.log("Failed to retrieve world settings! Using defaults.", twf.helper.MESSAGE_ERROR);
  729.                 }).done(function(result) {
  730.                     twf.data.worldSettings.speed = parseFloat($(result).find('speed').text());
  731.                     twf.data.worldSettings.unitSpeed = parseFloat($(result).find('unit_speed').text());
  732.                     twf.data.worldSettings.archer = $(result).find('archer').text() ? true : false;
  733.                     twf.data.worldSettings.knight = $(result).find('knight').text() ? true : false;
  734.                     twf.data.storeWorldLevel("worldSettings", twf.data.worldSettings);
  735.                     twf.helper.log("Succesfully retrieved world settings remotely.", twf.helper.MESSAGE_SUCCES);
  736.                 })
  737.             } else {
  738.                 twf.helper.log("Succesfully retrieved world settings.", twf.helper.MESSAGE_SUCCES);
  739.                 twf.data.worldSettings = tempWorldSettings;
  740.             }
  741.  
  742.         },
  743.         // store and load localstorage data at various levels
  744.         storeTownLevel: function(key, value) {
  745.             localStorage.setItem("twf_" + game_data.world + "_" + game_data.village.id + "_" + key, JSON.stringify(value));
  746.         },
  747.         loadTownLevel: function(key) {
  748.             return JSON.parse(localStorage.getItem("twf_" + game_data.world + "_" + game_data.village.id + "_" + key));
  749.         },
  750.         storeWorldLevel: function(key, value) {
  751.             localStorage.setItem("twf_" + game_data.world + "_" + key, JSON.stringify(value));
  752.         },
  753.         loadWorldLevel: function(key) {
  754.             return JSON.parse(localStorage.getItem("twf_" + game_data.world + "_" + key));
  755.         },
  756.         storeGlobal: function(key, value) {
  757.             localStorage.setItem("twf_" + key, JSON.stringify(value));
  758.         },
  759.         loadGlobal: function(key) {
  760.             return JSON.parse(localStorage.getItem("twf_" + key));
  761.         }
  762.     },
  763.     attacks: {
  764.         attacking: false,
  765.         continueAttack: true,
  766.         attackTemplates: {},
  767.         currentAttackTemplateTimestamp: null,
  768.         unitsPerAttack: {},
  769.         villageString: "",
  770.         villageArray: [],
  771.         currentVillage: null,
  772.         hiddenFrame: null, //todo init
  773.         hiddenFrameUrl: null,
  774.         init: function() {
  775.             this.hiddenFrameUrl = '/game.php?village=' + game_data.village.id + '&screen=place';
  776.             this.hiddenFrame = twf.helper.createHiddenFrame(this.hiddenFrameUrl, this.onFrameLoaded, "attack_hidden_frame");
  777.  
  778.             this.attackButton = $('#attackButton').click(this.attack); // this one is disabled in general init if reportFarmer is true
  779.             this.sAttackButton = $('#sAttackButton').click(this.stopAttack).hide();
  780.             this.rAttackButton = $('#resetAttack').click(this.resetAttack);
  781.             this.cAttackButton = $('#cAttackButton').click(function() {
  782.                 twf.helper.showAttackTemplate();
  783.             });
  784.  
  785.             this.loadAttackTemplates();
  786.             this.loadAttack();
  787.  
  788.             twf.helper.log("Attack module ready.", twf.helper.MESSAGE_SUCCES);
  789.         },
  790.         reportSendUnits: function(coords) {
  791.             console.debug("Entered reportSendUnits for " + coords);
  792.             console.debug("Actual coords: " + twf.attacks.villageArray[twf.attacks.attackTemplates[twf.attacks.currentAttackTemplateTimestamp].position]);
  793.             let frame = twf.attacks.hiddenFrame;
  794.             // reportfarming
  795.             let serverTime = twf.helper.getServerTime(frame);
  796.  
  797.             // we have data and it isn't too old
  798.             // console.log("servertime:" + serverTime.toUTCString() + " --- lastAttack: " + twf.reports.data[coords].lastAttack.toUTCString() );
  799.             // rewrite this because it prevents using available wall levels
  800.             // basically only check use this for expectedResources. We can fake it by setting that value to the exact amount that we to farm (e.g. maxUnits = minUnits)
  801.             if (twf.reports.data[coords]) {
  802.                 if (twf.reports.lostAllTroops(coords)) {
  803.                     //todo handle this
  804.                     // e.g. some algorithm to guess the wall level?
  805.                 }
  806.                 // we have report      
  807.                 let slowestUnit = null;
  808.                 // find slowest unit
  809.                 for (let unit in twf.attacks.unitsPerAttack) {
  810.                     if (twf.attacks.unitsPerAttack[unit] > 0 && (twf.data.travelTime[unit] > twf.data.travelTime[slowestUnit] || slowestUnit == null)) {
  811.                         slowestUnit = unit;
  812.                     }
  813.                 }
  814.  
  815.                 let expectedRes = null;
  816.                 let minLight = null;
  817.                 if (twf.reports.data[coords].scoutBuilding) {
  818.                     expectedRes = twf.reports.expectedRes(coords, slowestUnit, serverTime, false, false);
  819.                     minLight = twf.data.minLightPerWall[twf.reports.data[coords].buildingData.wall];
  820.  
  821.                 } else if (twf.reports.data[coords].resLeft != -1) {
  822.                     // we have resources scouted
  823.                     // just a guess
  824.                     let buildings = {
  825.                             wall: 1,
  826.                             storage: 3,
  827.                             hide: 3,
  828.                             iron: 3,
  829.                             clay: 3,
  830.                             wood: 3
  831.                         }
  832.                         // if we assumed level 1 or 0 and we lost all troops, our guess was incorrect (or it was spiked or something)
  833.                         // so adjust the wall level
  834.                     if (twf.reports.lostAllTroops(coords)) {
  835.                         buildings.wall = buildings.wall + 1;
  836.                     }
  837.                     expectedRes = twf.reports.expectedRes(coords, slowestUnit, serverTime, false, buildings);
  838.                     minLight = twf.data.minLightPerWall[buildings.wall];
  839.                 } else if (twf.reports.data[coords].resLeft == -1) {
  840.                     // we had a full haul
  841.                     let buildings = {
  842.                             wall: 1,
  843.                             storage: 3,
  844.                             hide: 3,
  845.                             iron: 3,
  846.                             clay: 3,
  847.                             wood: 3
  848.                         }
  849.                         // if we assumed level 1 or 0 and we lost all troops, our guess was incorrect (or it was spiked or something)
  850.                     if (twf.reports.lostAllTroops(coords)) {
  851.                         buildings.wall = buildings.wall + 1;
  852.                     }
  853.                     let assumedRes = 50;
  854.                     expectedRes = twf.reports.expectedRes(coords, slowestUnit, serverTime, assumedRes, buildings);
  855.                     minLight = twf.data.minLightPerWall[buildings.wall];
  856.                 }
  857.                 // if we cannot base expected res on how long ago the attack was sent (because it is longer than x ago)
  858.                 // just set expectesRes = 0, so we send the standard amount but based on the wall as well
  859.                 // we do not have to do this for wall, since that is already used if it is available and otherwise it's assumed
  860.                 if (serverTime - twf.reports.data[coords].lastAttack > 1000 * 60 * 60 * twf.data.settings.reportMaxUseAge) {
  861.                     expectedRes = 0;
  862.                     console.debug("Report older than " + twf.data.settings.reportMaxUseAge + "h, so expectedRes = 0.");
  863.                 }
  864.                 for (let unitType in twf.attacks.unitsPerAttack) {
  865.                     if (twf.attacks.continueAttack) {
  866.                         // skip if not in list to send
  867.                         if (twf.attacks.unitsPerAttack[unitType] == 0) {
  868.                             continue;
  869.                         }
  870.                         let unitsLeft = frame.contents().find('#units_entry_all_' + unitType).html();
  871.                         unitsLeft = parseInt(unitsLeft.substring(1, unitsLeft.length - 1));
  872.  
  873.                         let fullHaulUnits = Math.ceil(expectedRes / twf.data.carryCapacity[unitType]);
  874.                         //console.debug("Expected: " + expectedRes + ". Carry (" + unitType + "): " + twf.data.carryCapacity[unitType] + ". fullHaul: " + fullHaulUnits);
  875.                         let minUnits = twf.attacks.unitsPerAttack[unitType];
  876.  
  877.                         if (unitType == "light") {
  878.                             // if light, adjust for wall
  879.                             minUnits = Math.max(minUnits, minLight);
  880.                         }
  881.                         if (unitType == "spy") {
  882.                             // if spy, just send the minimum (i.e. overwrite whatever we calculated)
  883.                             fullHaulUnits = twf.attacks.unitsPerAttack[unitType];
  884.                         }
  885.  
  886.  
  887.                         // dont waste an extra attack on an attack
  888.                         if (minUnits > fullHaulUnits + 5 && minUnits > 10) {
  889.                             twf.helper.log("Not sending attack to " + coords + ". Min " + unitType + " = " + minUnits + " while we need only " + fullHaulUnits + " for a full haul.", twf.helper.MESSAGE_WARNING);
  890.                             //twf.attacks.continueAttack = false; // to prevent trying to send
  891.                             // does not work
  892.                             twf.attacks.ignoreVillage();
  893.                             console.debug("started ignoreVillage from within reportSendUnits");
  894.                             return false;
  895.                         }
  896.                         // not enough units
  897.                         else if (minUnits > unitsLeft) {
  898.                             if (unitType == "spy" && twf.attacks.attackTemplates[twf.attacks.currentAttackTemplateTimestamp].ignoreScouts) {
  899.                                 //console.debug("No spies. Trying to send...");
  900.                                 twf.attacks.continueAttack = true;
  901.                                 continue;
  902.                             } else if (twf.data.settings.waitForTroops) {
  903.                                 twf.helper.log("Not enough of " + unitType + ". Waiting for troops", twf.helper.MESSAGE_DEFAULT);
  904.                             } else {
  905.                                 twf.helper.log('Not enough units of type: ' + unitType, twf.helper.MESSAGE_ERROR);
  906.                                 twf.helper.stopEverything();
  907.                             }
  908.                             twf.attacks.continueAttack = false;
  909.                             return true; // we did not skip a village
  910.                         }
  911.                         // we have as many as we want,
  912.                         else if (fullHaulUnits <= unitsLeft) {
  913.                             console.debug("Set " + unitType + " to " + Math.max(minUnits, fullHaulUnits) + " (Available: " + unitsLeft + ", minimum: " + minUnits + ")");
  914.                             frame.contents().find('#unit_input_' + unitType).val(Math.max(minUnits, fullHaulUnits));
  915.                             twf.attacks.continueAttack = true;
  916.                         } else {
  917.                             console.debug("Set " + unitType + " to " + unitsLeft + " (Preferred but unable: " + fullHaulUnits + ")");
  918.                             frame.contents().find('#unit_input_' + unitType).val(unitsLeft);
  919.                             twf.attacks.continueAttack = true;
  920.                         }
  921.                     }
  922.                 }
  923.                 console.debug("Done inputting units. twf.attacks.continueAttack = " + twf.attacks.continueAttack);
  924.                 return true; // we did not skip a village
  925.             } else {
  926.                 // no report
  927.                 // todo check wall level and all units lost, else send just a scout?
  928.  
  929.                 // we have no report data or it is too old -> normal attacks
  930.                 // maybe change to scout and settings and stuff
  931.                 console.debug("No data, switching to normal farming.");
  932.                 twf.attacks.normalSendUnits(coords);
  933.                 return true;
  934.                 // no report, force scout? skip?
  935.                 // degrade to standard farming
  936.             }
  937.  
  938.         },
  939.         // handles inputting and checking of available units
  940.         normalSendUnits: function(coord) {
  941.             for (let unitType in twf.attacks.unitsPerAttack) {
  942.                 if (twf.attacks.continueAttack) {
  943.                     twf.attacks.continueAttack = twf.attacks.sendUnit(unitType, coord);
  944.                 }
  945.             }
  946.         },
  947.         sendUnit: function(unitType, coords) {
  948.             let unitsToSend = this.unitsPerAttack;;
  949.             let frame = this.hiddenFrame;
  950.             if (unitsToSend[unitType] == 0) {
  951.                 return true;
  952.             }
  953.  
  954.             let unitsLeft = frame.contents().find('#units_entry_all_' + unitType).html();
  955.             unitsLeft = parseInt(unitsLeft.substring(1, unitsLeft.length - 1));
  956.             // can also use [data-all-count]
  957.  
  958.             // normal farming
  959.             if (unitsLeft >= unitsToSend[unitType]) {
  960.                 frame.contents().find('#unit_input_' + unitType).val(unitsToSend[unitType]);
  961.                 return true
  962.                     // if we are allowed to skip spies, skip them!
  963.             } else if (unitType == "spy" && twf.attacks.attackTemplates[twf.attacks.currentAttackTemplateTimestamp].ignoreScouts) {
  964.                 twf.helper.log("Not enough spies. Trying to send without spies.", twf.helper.MESSAGE_DEFAULT);
  965.                 return true;
  966.             } else {
  967.                 if (twf.data.settings.waitForTroops) {
  968.                     twf.helper.log('Not enough units of type: ' + unitType + ', waiting for some to return!', twf.helper.MESSAGE_DEFAULT);
  969.                 } else {
  970.                     twf.helper.log('Not enough units of type: ' + unitType, twf.helper.MESSAGE_ERROR);
  971.                     this.stopAttack();
  972.                 }
  973.                 return false
  974.             }
  975.         },
  976.         attack: function() {
  977.             console.debug("Entered attacks.attack");
  978.             twf.attacks.attackButton.hide();
  979.             twf.attacks.sAttackButton.show();
  980.  
  981.             let coord = twf.attacks.villageArray[twf.attacks.getPosition()];
  982.             twf.attacks.continueAttack = true;
  983.             let noSkippedVil = true;
  984.             // fill in units if available
  985.             if (!twf.data.settings.reportFarmer) {
  986.                 twf.attacks.normalSendUnits(coord);
  987.             } else {
  988.                 // this needs all checks
  989.                 // e.g. wall level, did all troops die, etc, etc
  990.                 if (twf.reports.data[coord] && twf.reports.data[coord].buildingData && twf.reports.data[coord].buildingData.wall >= twf.data.minLightPerWall.length) {
  991.                     console.debug("Ignore " + coords + " because wall too high.");
  992.                     return twf.attacks.ignoreVillage();
  993.                     // does this work?
  994.                 }
  995.                 // allow report reading if we are botting
  996.                 if (twf.data.settings.waitForTroops) {
  997.                     twf.reports.allowAutomaticReading = true;
  998.                 }
  999.  
  1000.                 noSkippedVil = twf.attacks.reportSendUnits(coord);
  1001.             }
  1002.             if (twf.attacks.continueAttack && noSkippedVil) {
  1003.                 console.debug("In attacks.attack, beofre pressing #target_attack");
  1004.                 twf.attacks.hiddenFrame.contents().find('.target-input-field.target-input-autocomplete.ui-autocomplete-input').val(coord);
  1005.                 twf.attacks.hiddenFrame.contents().find('#target_attack').click();
  1006.                 twf.attacks.attacking = true;
  1007.                 twf.helper.spinner.show();
  1008.                 twf.helper.log("Attacking [" + coord + "]!", twf.helper.MESSAGE_SUCCES);
  1009.                 return true;
  1010.                 // not enough units but botting, so wait
  1011.             } else if (twf.data.settings.waitForTroops && noSkippedVil) {
  1012.                 if (!twf.data.timers.attackPollTimer) {
  1013.                     // if we haven't got a timer, run it
  1014.  
  1015.                     // this finds the time of the first returning attacks
  1016.                     let rows = twf.attacks.hiddenFrame.contents().find('[data-command-type="return"], [data-command-type="cancel"]').parents('.command-row');
  1017.                     if (rows.length > 0) {
  1018.                         let time = rows.eq(rows.length - 1).find('[data-endtime]').html().split(":");
  1019.                         time[0] = twf.helper.leadingZero(time[0]);
  1020.  
  1021.                         let secondsToArrival = 3600 * parseInt(time[0]) + 60 * parseInt(time[1]) + parseInt(time[2]) + 1;
  1022.  
  1023.                         // create a variance variable (triangular?) distributed [-earlyTime, lateTime],
  1024.                         let pollVariance = 0;
  1025.                         if (twf.data.settings.pollLate) {
  1026.                             pollVariance += twf.helper.getRandomSecondsBetween(twf.data.settings.minPollAttack, twf.data.settings.minPollAttack + twf.data.settings.pollLateSeconds);
  1027.                         }
  1028.  
  1029.                         let timeToCheck = Math.floor((secondsToArrival + pollVariance + 1));
  1030.                         let s = timeToCheck % 60;
  1031.                         let m = Math.floor((timeToCheck / 60) % 60);
  1032.                         let h = Math.floor((timeToCheck / 3600));
  1033.  
  1034.                         twf.data.timers.attackPollTimer = window.setTimeout(twf.attacks.poll, timeToCheck * 1000);
  1035.                         twf.helper.log("Arrival in: " + time.join(":") + ". Checking in: " + h + ":" + twf.helper.leadingZero(m) + ":" + twf.helper.leadingZero(s), twf.helper.MESSAGE_DEFAULT);
  1036.                     } else {
  1037.                         var waitTime = 60;
  1038.                         twf.data.timers.attackPollTimer = window.setTimeout(twf.attacks.poll, waitTime * 1000);
  1039.                         twf.helper.log("No arrivals :(. Checking in " + waitTime + " seconds.");
  1040.                     }
  1041.                 }
  1042.             }
  1043.         },
  1044.         poll: function() {
  1045.             twf.data.timers.attackPollTimer = null;
  1046.             twf.attacks.continueAttack = true;
  1047.             twf.attacks.attacking = true;
  1048.             twf.attacks.hiddenFrame.attr('src', twf.attacks.hiddenFrame.attr('src'));
  1049.         },
  1050.         // also stops timer
  1051.         stopAttack: function() {
  1052.             twf.attacks.attackButton.show();
  1053.             twf.attacks.sAttackButton.hide();
  1054.             twf.attacks.attacking = false;
  1055.             twf.attacks.continueAttack = false;
  1056.             if (twf.data.timers.attackPollTimer) { //remove polling timer
  1057.                 window.clearTimeout(twf.data.timers.attackPollTimer);
  1058.                 twf.data.timers.attackPollTimer = null;
  1059.             }
  1060.             if (twf.attacks.getPosition() >= twf.attacks.villageArray.length) {
  1061.                 twf.helper.log("Cycle finished. Resetting to first village.", twf.helper.MESSAGE_DEFAULT);
  1062.                 twf.attacks.resetAttack(true);
  1063.             }
  1064.             if (twf.data.timers.reportPollTimer) {
  1065.                 window.clearTimeout(twf.data.timers.reportPollTimer);
  1066.                 twf.data.timers.reportPollTimer = null;
  1067.             }
  1068.             twf.reports.allowAutomaticReading = false;
  1069.             twf.helper.log("Bot has been stopped.", twf.helper.MESSAGE_DEFAULT);
  1070.         },
  1071.         resetAttack: function(skipLog) {
  1072.             if (!skipLog) {
  1073.                 twf.helper.log("Resetting to first village.", twf.helper.MESSAGE_DEFAULT);
  1074.             }
  1075.             twf.attacks.attackTemplates[twf.attacks.currentAttackTemplateTimestamp].position = 0;
  1076.             $('#attacked_villages').text(twf.attacks.getPosition() + 1 + "/" + twf.attacks.villageArray.length);
  1077.             twf.data.storeTownLevel('attackTemplates', twf.attacks.attackTemplates, true)
  1078.         },
  1079.         onFrameLoaded: function() {
  1080.             console.debug("Entered attacks.onFrameLoaded");
  1081.             try {
  1082.                 twf.helper.spinner.fadeOut();
  1083.                 twf.helper.checkBotProtection(this.hiddenFrame);
  1084.  
  1085.                 // check on which screen we are and do something accordingly
  1086.                 // confirm attack screen
  1087.                 let confirmAttack = twf.attacks.hiddenFrame.contents().find("#troop_confirm_go");
  1088.                 let error = twf.attacks.hiddenFrame.contents().find('.error_box');
  1089.                 // we are attacking a player
  1090.                 let isPlayerEn = twf.attacks.hiddenFrame.contents().find('table.vis td:contains("Player:")');
  1091.                 let isPlayerNl = twf.attacks.hiddenFrame.contents().find('table.vis td:contains("Speler:")');
  1092.                 if (error && error.length > 0) {
  1093.                     let coords = twf.attacks.villageArray[twf.attacks.getPosition()];
  1094.                     twf.helper.log("Error: " + error.html() + " --- Continuing next village (skipped [" + coords + "])", twf.helper.MESSAGE_ERROR);
  1095.                     return twf.attacks.ignoreVillage();
  1096.                 }
  1097.                 if ((isPlayerEn.length > 0 || isPlayerNl.length > 0) && !twf.data.settings.attackPlayers) {
  1098.                     let coords = twf.attacks.villageArray[twf.attacks.getPosition()];
  1099.                     twf.helper.log("Owner is a player! Continuing next village (skipped [" + coords + "])", twf.helper.MESSAGE_ERROR);
  1100.                     return twf.attacks.ignoreVillage();
  1101.                 }
  1102.                 // select troop screen
  1103.                 if (confirmAttack.size() == 0) {
  1104.  
  1105.                     //============= BEGIN REPORT READING
  1106.                     // basically, if we are in place, also extract next arriving attack and set a timer
  1107.                     // TODO MAYBE CHANGE THIS TO JUST DO IT AT THE SAME TIME AS SENDING ATTACKS
  1108.                     if (twf.data.settings.reportFarmer && twf.reports.allowAutomaticReading && twf.data.timers.reportPollTimer == null && !twf.reports.currentlyReading) {
  1109.                         // automatically read report
  1110.                         if (twf.attacks.hiddenFrame.contents().find('[data-command-type="attack"]').length > 0) {
  1111.                             let rows = twf.attacks.hiddenFrame.contents().find('[data-command-type="attack"]').parents('.command-row');
  1112.                             let time = rows.eq(rows.length - 1).find('[data-endtime]').html().split(":");
  1113.                             time[0] = twf.helper.leadingZero(time[0]);
  1114.  
  1115.                             let secondsToArrival = 3600 * parseInt(time[0]) + 60 * parseInt(time[1]) + parseInt(time[2]) + 1;
  1116.  
  1117.                             // create a variance variable (triangular?) distributed [-earlyTime, lateTime],
  1118.                             let pollVariance = 0;
  1119.  
  1120.                             if (twf.data.settings.pollLate) {
  1121.                                 pollVariance += twf.helper.getRandomSecondsBetween(twf.data.settings.minPollReport, twf.data.settings.minPollReport + twf.data.settings.pollLateSeconds * 5);
  1122.                             }
  1123.  
  1124.                             let timeToCheck = Math.floor((secondsToArrival + pollVariance + 1));
  1125.                             let s = timeToCheck % 60;
  1126.                             let m = Math.floor((timeToCheck / 60) % 60);
  1127.                             let h = Math.floor((timeToCheck / 3600));
  1128.  
  1129.                             twf.data.timers.reportPollTimer = window.setTimeout(function() {
  1130.                                 twf.data.timers.reportPollTimer = null;
  1131.                                 twf.reports.startReading();
  1132.                             }, timeToCheck * 1000);
  1133.                             twf.helper.log("Attack arriving in: " + time.join(":") + ". Reading reports: " + h + ":" + twf.helper.leadingZero(m) + ":" + twf.helper.leadingZero(s), twf.helper.MESSAGE_DEFAULT);
  1134.                         }
  1135.                     }
  1136.                     // ========== END REPORT READING
  1137.  
  1138.                     twf.attacks.loadAttack(twf.attacks.currentAttackTemplateTimestamp);
  1139.                     twf.attacks.showAttack();
  1140.                     if (twf.attacks.attacking && twf.attacks.continueAttack) {
  1141.                         twf.attacks.attack();
  1142.                     }
  1143.                 } else {
  1144.                     console.debug("Confirming attack for " + twf.attacks.villageArray[twf.attacks.getPosition()]);
  1145.                     //update attack information and click confirm
  1146.                     twf.attacks.attackTemplates[twf.attacks.currentAttackTemplateTimestamp].position = twf.attacks.getPosition() + 1;
  1147.                     if (twf.attacks.getPosition() >= twf.attacks.villageArray.length) {
  1148.                         if (twf.data.settings.resetToFirst) {
  1149.                             twf.attacks.resetAttack()
  1150.                         } else {
  1151.                             twf.attacks.stopAttack()
  1152.                         }
  1153.                     }
  1154.                     twf.data.storeTownLevel('attackTemplates', twf.attacks.attackTemplates);
  1155.                     twf.helper.spinner.show();
  1156.                     confirmAttack.click();
  1157.                 }
  1158.             } catch (error) {
  1159.                 //console.error(error);
  1160.                 //twf.helper.stopEverything();
  1161.                 console.error(error);
  1162.                 alert("BOT PROTECTION?\n" + new Date() + "\n" + error);
  1163.             }
  1164.         },
  1165.         // shows the currently loaded attack in the panel and bind clicking on it
  1166.         showAttack: function() {
  1167.             $("#attackUnits").html("");
  1168.             for (let i in this.unitsPerAttack) {
  1169.                 if (this.unitsPerAttack[i] > 0) {
  1170.                     var unitsAvailable = this.hiddenFrame.contents().find('#units_entry_all_' + i).html() || "???";
  1171.                     var unitsText = i + ': ' + this.unitsPerAttack[i] + ' (' + unitsAvailable.trim().substring(1, unitsAvailable.length - 1) + ')';
  1172.                     $('<img />').attr('src', 'https://dsnl.innogamescdn.com/8.67/31807/graphic/unit/unit_' + i + '.png').attr('title', unitsText).attr('alt', unitsAvailable).appendTo($('#attackUnits')).click(function(event) {
  1173.                         twf.helper.showAttackTemplate(twf.attacks.currentAttackTemplateTimestamp);
  1174.                         $('#template_popup #unit_input_' + i).focus().select();
  1175.                     });
  1176.                     $('<span />').html('(' + this.unitsPerAttack[i] + ') ').css({
  1177.                         'color': '#000'
  1178.                     }).appendTo($("#attackUnits"));
  1179.                 }
  1180.             }
  1181.         },
  1182.         // loads an attack from a template to prepare it for take0off
  1183.         loadAttack: function(timestamp) {
  1184.             // no templates -> error
  1185.             if (!this.attackTemplates) {
  1186.                 //twf.helper.log("No attack templates available!", twf.helper.MESSAGE_WARNING);
  1187.                 return;
  1188.             }
  1189.             // no ts -> load first one
  1190.             if (!timestamp) {
  1191.                 timestamp = Object.keys(this.attackTemplates)[0];
  1192.             }
  1193.             // else load the one given
  1194.             this.currentAttackTemplateTimestamp = timestamp;
  1195.             let attack = this.attackTemplates[timestamp];
  1196.             $("#attackName").html(attack.name);
  1197.             for (let i in twf.data.unitTypes) {
  1198.  
  1199.                 this.unitsPerAttack[twf.data.unitTypes[i]] = attack.units[twf.data.unitTypes[i]];
  1200.                 //console.log(this.unitsPerAttack)
  1201.             }
  1202.             this.villageString = attack.coords.join(" ");
  1203.             this.villageArray = attack.coords;
  1204.             this.villageArray = twf.helper.sortByDistance(this.villageArray, game_data.village.coord);
  1205.             this.showAttack();
  1206.             $('#attacked_villages').text(this.getPosition() + 1 + "/" + this.villageArray.length);
  1207.             return attack;
  1208.         },
  1209.         // fills the list of attack templates
  1210.         populateTemplateList: function() {
  1211.             $("#attackList").children().remove();
  1212.             for (let timestamp in this.attackTemplates) {
  1213.                 let row = $('<tr/>').appendTo($("#attackList"));
  1214.                 $('<td title="Load this attack" />').html('L').bind('click', {
  1215.                     attack: timestamp
  1216.                 }, function(event) {
  1217.                     twf.attacks.loadAttack(event.data.attack);
  1218.                 }).css({
  1219.                     'border': '1px solid #0F0',
  1220.                     'width': '10px',
  1221.                     'cursor': 'pointer',
  1222.                     'color': '#0F0',
  1223.                     'background-color': '#000'
  1224.                 }).appendTo(row);
  1225.                 $('<td>' + this.attackTemplates[timestamp].name + '</td>').appendTo(row);
  1226.                 $('<td title="Remove this attack (CAN NOT BE UNDONE)" />').html('X').bind('click', {
  1227.                     attack: timestamp
  1228.                 }, function(event) {
  1229.                     if (confirm("Are you sure you want to remove '" + twf.attacks.attackTemplates[event.data.attack].name + "'?")) {
  1230.                         twf.attacks.removeAttackTemplate(event.data.attack);
  1231.                     }
  1232.                 }).css({
  1233.                     'border': '1px solid #f00',
  1234.                     'width': '10px',
  1235.                     'cursor': 'pointer',
  1236.                     'color': '#f00',
  1237.                     'background-color': '#000'
  1238.                 }).appendTo(row);
  1239.             }
  1240.         },
  1241.         // saves an attack template and reloads the templatelist
  1242.         saveAttackTemplate: function() {
  1243.             // check if data is valid
  1244.             if (!$("#unit_input_name").val() || $("#unit_input_coords").val().match(/[\d{3}\|\d{3}\s]+/) === null) {
  1245.                 $(".quest-summary").css("background-color", "red");
  1246.                 $(".quest-summary")[0].innerHTML = "Please make sure you filled in coords and name correctly, then try again!";
  1247.                 return false;
  1248.             }
  1249.             // fill table of units
  1250.             let unitsToSend = {};
  1251.             for (let i in twf.data.unitTypes) {
  1252.                 unitsToSend[twf.data.unitTypes[i]] = parseInt($("#unit_input_" + twf.data.unitTypes[i]).val()) || 0;
  1253.             }
  1254.             //clear coords from empty values
  1255.             let tempCoords1 = $("#unit_input_coords").val().trim().split(" ");
  1256.             let tempCoords2 = [];
  1257.             for (let i in tempCoords1) {
  1258.                 if (tempCoords1[i]) {
  1259.                     tempCoords2.push(tempCoords1[i]);
  1260.                 }
  1261.             }
  1262.             // fill template
  1263.             let template = {
  1264.                     name: $("#unit_input_name").val(),
  1265.                     units: unitsToSend,
  1266.                     coords: tempCoords2,
  1267.                     position: $("#unit_input_position").val(),
  1268.                     ignoreScouts: $('#ignore_scouts').is(":checked"),
  1269.                 }
  1270.                 // save
  1271.             if (!twf.attacks.attackTemplates) { // no attacktemplates so it is reset to null when loading
  1272.                 // so we have to instantiate it now
  1273.                 twf.attacks.attackTemplates = {};
  1274.             }
  1275.             twf.attacks.attackTemplates[$("#unit_input_timestamp").val()] = template;
  1276.             twf.data.storeTownLevel("attackTemplates", this.attackTemplates);
  1277.             twf.helper.log("Saved the new attack template: " + template.name, twf.helper.MESSAGE_SUCCES);
  1278.             this.populateTemplateList();
  1279.             this.loadAttack($("#unit_input_timestamp").val());
  1280.             $("#template_popup").hide();
  1281.         },
  1282.         // removes an attack template from storage and reloads the templatelist
  1283.         removeAttackTemplate: function(timestamp) {
  1284.             delete this.attackTemplates[timestamp]
  1285.             if (this.currentAttackTemplateTimestamp == timestamp) {
  1286.                 this.loadAttack();
  1287.             }
  1288.             twf.data.storeTownLevel("attackTemplates", this.attackTemplates);
  1289.             this.populateTemplateList();
  1290.         },
  1291.         // loads all attack templates from storage
  1292.         loadAttackTemplates: function() {
  1293.             this.attackTemplates = twf.data.loadTownLevel("attackTemplates");
  1294.             this.populateTemplateList();
  1295.             if (this.attackTemplates) {
  1296.                 let l = Object.keys(this.attackTemplates).length;
  1297.                 twf.helper.log("Loaded " + l + " attack template" + ((l > 1) ? "s." : "."), twf.helper.MESSAGE_SUCCES);
  1298.             } else {
  1299.                 twf.helper.log("No attack templates to be loaded. <b> Create one first! </b>", twf.helper.MESSAGE_WARNING);
  1300.             }
  1301.         },
  1302.         // returns the current village to attack (persistent throughout runs)
  1303.         getPosition: function() {
  1304.             return parseInt(this.attackTemplates[this.currentAttackTemplateTimestamp].position);
  1305.         },
  1306.         ignoreVillage: function() {
  1307.             console.group("ignoreVillage");
  1308.             // before update
  1309.             console.log("BEFORE UPDATE");
  1310.             console.log("attackTemplates: ", this.attackTemplates);
  1311.             console.log("url: ", this.hiddenFrame[0].src);
  1312.             console.log("villageArray: ", this.villageArray);
  1313.             console.log("attTemplate timestamp: ", this.currentAttackTemplateTimestamp);
  1314.             console.log("attTemplate[timestamp].pos: ", this.attackTemplates[this.currentAttackTemplateTimestamp].position);
  1315.             console.log("getPosition: ", this.getPosition());
  1316.             console.log("currentCoords: ", this.villageArray[this.getPosition()]);
  1317.  
  1318.             this.attackTemplates[this.currentAttackTemplateTimestamp].position = this.getPosition() + 1;
  1319.  
  1320.             console.log("===========================");
  1321.             console.log("AFTER UPDATE");
  1322.             console.log("attackTemplates: ", this.attackTemplates);
  1323.             console.log("url: ", this.hiddenFrame[0].src);
  1324.             console.log("villageArray: ", this.villageArray);
  1325.             console.log("attTemplate timestamp: ", this.currentAttackTemplateTimestamp);
  1326.             console.log("attTemplate[timestamp].pos: ", this.attackTemplates[this.currentAttackTemplateTimestamp].position);
  1327.             console.log("getPosition", this.getPosition());
  1328.             console.log("currentCoords: ", this.villageArray[this.getPosition()]);
  1329.             //console.error("IGNORED A VILLAGE, BUT STILL PRESS OKAY");
  1330.             //console.error("Village to be ignored: " + this.villageArray[this.attackTemplates[this.currentAttackTemplateTimestamp].position - 1] + ". Village to continue with: " + this.villageArray[this.attackTemplates[this.currentAttackTemplateTimestamp].position]);
  1331.             if (this.getPosition() >= this.villageArray.length) {
  1332.                 if (twf.data.settings.resetToFirst) {
  1333.                     this.resetAttack();
  1334.                 } else {
  1335.                     this.stopAttack();
  1336.                 }
  1337.             }
  1338.  
  1339.             twf.data.storeTownLevel('attackTemplates', twf.attacks.attackTemplates);
  1340.             console.log("===========================");
  1341.             console.log("BEFORE REFRESH");
  1342.             console.log("url: ", this.hiddenFrame[0].src);
  1343.             console.log("url New : ", this.hiddenFrameUrl);
  1344.             console.groupEnd();
  1345.             this.hiddenFrame.attr('src', this.hiddenFrameUrl);
  1346.             console.log("Returning true in ignoreVillage()");
  1347.             return true;
  1348.         }
  1349.     },
  1350.     helper: {
  1351.         MESSAGE_ERROR: 0,
  1352.         MESSAGE_SUCCES: 1,
  1353.         MESSAGE_DEFAULT: 2,
  1354.         MESSAGE_WARNING: 3,
  1355.         splash: null,
  1356.         stickyPanel: false,
  1357.         panelInTransit: false,
  1358.         panelOut: false,
  1359.         init: function() {
  1360.             //append all html snippets and set their click events
  1361.             $("head").append(twf.html.css);
  1362.             $(twf.html.templatePopup).appendTo('body').hide();
  1363.             $(twf.html.settingsPopup).appendTo('body').hide();
  1364.             this.panel = $(twf.html.panel).appendTo('body').bind("mouseenter", twf.helper.panelMouseIn).bind("mouseleave", twf.helper.panelMouseOut);
  1365.  
  1366.             this.messages = $('#messages');
  1367.             this.spinner = $('#loading');
  1368.  
  1369.             $('#save_attack_template').click(function() {
  1370.                 twf.attacks.saveAttackTemplate();
  1371.             });
  1372.             $('.close_template_button').click(function() {
  1373.                 $('#template_popup').hide();
  1374.             })
  1375.             $('.close_settings_button').click(function() {
  1376.                 $('#settings_popup').hide();
  1377.             })
  1378.             $('#save_settings').click(function() {
  1379.                 twf.data.saveSettings();
  1380.             });
  1381.             $('#show_settings').click(function() {
  1382.                 twf.helper.showSettings();
  1383.             });
  1384.             $("#wallbreaker").click(function() {
  1385.                 twf.helper.log("This module is not finished yet.", twf.helper.MESSAGE_ERROR);
  1386.             })
  1387.  
  1388.             $('#tack').click(this.toggleSticky).find('.on').hide();
  1389.  
  1390.             $('#attackUnits').attr('title', 'Click the images to edit the template');
  1391.  
  1392.             twf.helper.log("Helper module ready.", twf.helper.MESSAGE_SUCCES);
  1393.         },
  1394.         createHiddenFrame: function(url, onload, frameId) {
  1395.             return $('<iframe id="' + frameId + '" src="' + url + '" />').load(onload).css({
  1396.                 width: '100px',
  1397.                 height: '100px',
  1398.                 position: 'absolute',
  1399.                 left: '-1000px'
  1400.             }).appendTo('body').hide();
  1401.         },
  1402.         showSettings: function() {
  1403.             // checkboxes
  1404.             $('#reset_to_first').prop('checked', twf.data.settings.resetToFirst);
  1405.             $('#wait_for_troops').prop('checked', twf.data.settings.waitForTroops);
  1406.             $('#attack_players').prop('checked', twf.data.settings.resetToFirst);
  1407.             $('#autostop').prop('checked', twf.data.settings.autoStop);
  1408.             $('#poll_late').prop('checked', twf.data.settings.pollLate);
  1409.             $('#discount_time').prop('checked', twf.data.settings.discountTime);
  1410.             $('#report_farmer').prop('checked', twf.data.settings.reportFarmer);
  1411.  
  1412.             //values
  1413.             $('#autostop_minutes').val(twf.data.settings.autoStopMinutes);
  1414.             $('#poll_late_seconds').val(twf.data.settings.pollLateSeconds);
  1415.             $('#discount_factor').val(twf.data.settings.discountFactor == 1 ? twf.data.settings.discountFactor + ".00" : twf.data.settings.discountFactor);
  1416.             $("#report_max_read_age").val(twf.data.settings.reportMaxReadAge);
  1417.             $("#report_max_use_age").val(twf.data.settings.reportMaxUseAge);
  1418.  
  1419.             //show
  1420.             $('#settings_popup').show();
  1421.         },
  1422.         showAttackTemplate: function(timestamp) {
  1423.             // if timestamp, load old if exists
  1424.             if (timestamp && twf.attacks.attackTemplates.hasOwnProperty(timestamp)) {
  1425.                 $("#template_popup #unit_input_timestamp").val(timestamp);
  1426.                 $("#template_popup #unit_input_position").val(twf.attacks.attackTemplates[timestamp].position);
  1427.                 $("#template_popup #unit_input_coords").val(twf.attacks.attackTemplates[timestamp].coords.join(" ").trim());
  1428.                 $("#template_popup #unit_input_name").val(twf.attacks.attackTemplates[timestamp].name);
  1429.                 for (let i in twf.data.unitTypes) {
  1430.                     $("#template_popup #unit_input_" + twf.data.unitTypes[i]).val(twf.attacks.attackTemplates[timestamp].units[twf.data.unitTypes[i]])
  1431.                 }
  1432.                 $('#ignore_scouts').prop('checked', twf.attacks.attackTemplates[timestamp].ignoreScouts);
  1433.             } else {
  1434.                 $("#template_popup #unit_input_timestamp").val(+new Date());
  1435.                 $("#template_popup #unit_input_position").val('0');
  1436.                 $("#template_popup #unit_input_coords").val('');
  1437.                 $("#template_popup #unit_input_name").val('');
  1438.                 for (let i in twf.data.unitTypes) {
  1439.                     $("#template_popup #unit_input_" + twf.data.unitTypes[i]).val('')
  1440.                 }
  1441.                 $('#ignore_scouts').prop('checked', false);
  1442.             }
  1443.             $("#template_popup").show();
  1444.         },
  1445.         panelMouseIn: function() {
  1446.             if (!twf.helper.stickyPanel && !twf.helper.panelInTransit && !twf.helper.panelOut) {
  1447.                 twf.helper.panelInTransit = true;
  1448.                 twf.helper.panel.animate({
  1449.                     "right": "+=314px"
  1450.                 }, "slow", function() {
  1451.                     twf.helper.panelInTransit = false;
  1452.                     twf.helper.panelOut = true;
  1453.                 })
  1454.             }
  1455.         },
  1456.         panelMouseOut: function() {
  1457.             if (!twf.helper.stickyPanel && !twf.helper.panelInTransit && twf.helper.panelOut) {
  1458.                 twf.helper.panelInTransit = true;
  1459.                 twf.helper.panel.animate({
  1460.                     "right": "-=314px"
  1461.                 }, "slow", function() {
  1462.                     twf.helper.panelInTransit = false;
  1463.                     twf.helper.panelOut = false;
  1464.                 })
  1465.             }
  1466.         },
  1467.         toggleSticky: function() {
  1468.             twf.helper.stickyPanel = !twf.helper.stickyPanel;
  1469.             $('#tack').find('.on').toggle();
  1470.             $('#tack').find('.off').toggle();
  1471.         },
  1472.         sortByDistance: function(arr, me) {
  1473.             return arr.sort(function(a, b) {
  1474.                 a = a.split("|");
  1475.                 b = b.split("|");
  1476.                 let from = me.split("|");
  1477.                 let d1 = (a[0] - from[0]) * (a[0] - from[0]) + (a[1] - from[1]) * (a[1] - from[1]);
  1478.                 let d2 = (b[0] - from[0]) * (b[0] - from[0]) + (b[1] - from[1]) * (b[1] - from[1]);
  1479.                 return d1 - d2;
  1480.             });
  1481.  
  1482.         },
  1483.         getServerTime: function(frame) {
  1484.             let serverTime = frame.contents().find('#serverTime').text().split(":"); //[7; 14; 05] for example
  1485.             serverTime[0] = twf.helper.leadingZero(parseInt(serverTime[0])) // add leading zero to hour
  1486.             let serverDate = frame.contents().find('#serverDate').text().split("/").reverse().join("-");
  1487.             //console.debug(serverDate);
  1488.             // assume everything in UTC, but it does not matter since their timezone is always the same.
  1489.             return new Date(serverDate + "T" + serverTime.join(":") + "Z");
  1490.         },
  1491.         getResPerHour: function(level) {
  1492.             if (level == 0) {
  1493.                 return 5;
  1494.             }
  1495.             return 30 * Math.pow(1.163118, level - 1);
  1496.         },
  1497.         getTravelTimeInMinutes: function(distance, unit) {
  1498.             return distance * twf.data.travelTime[unit] / twf.data.worldSettings.speed / twf.data.worldSettings.unitSpeed;
  1499.         },
  1500.         getDistance: function(opp, me) {
  1501.             let a = opp.split("|");
  1502.             let b = me.split("|");
  1503.             return Math.sqrt((a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1]));
  1504.         },
  1505.         getMaxStorage: function(level) {
  1506.             return Math.round(1000 * Math.pow(1.2294934, (level - 1)));
  1507.         },
  1508.         getMaxHiding: function(level) {
  1509.             return Math.round(150 * Math.pow((4 / 3), (level - 1)));
  1510.         },
  1511.         leadingZero: function(a) {
  1512.             return (a < 10) ? '0' + a : a;
  1513.         },
  1514.         getRandomSecondsBetween: function(low, high) {
  1515.             if (low >= high) {
  1516.                 console.log("Wrong value for low, high: " + [low, high]);
  1517.                 return null;
  1518.             }
  1519.             return Math.random() * (high - low) + low;
  1520.         },
  1521.         checkBotProtection: function(frame) {
  1522.             if (!frame) return true;
  1523.  
  1524.             if (frame.contents().find(".g-recaptcha, #bot_check").length > 0 || $(".g-recaptcha, #bot_check").length > 0 || frame.contents().find("body").data("bot-protect")) {
  1525.                 console.warn("BOT PROTECTION DETECTED!");
  1526.                 this.stopEverything();
  1527.                 alert("BOT PROTECTION DETECTED! --- " + new Date());
  1528.             }
  1529.         },
  1530.         stopEverything: function() {
  1531.             twf.attacks.stopAttack();
  1532.             twf.reports.stopReading();
  1533.             if (twf.data.timers.reportPollTimer) {
  1534.                 clearTimeout(twf.data.timers.reportPollTimer);
  1535.                 console.debug("Stopped reportPollTimer.");
  1536.             }
  1537.         },
  1538.         log: function(text, messageType) {
  1539.             let date = new Date();
  1540.             let message = '<i>' + twf.helper.leadingZero(date.getHours()) + ':' + twf.helper.leadingZero(date.getMinutes()) + ':' + twf.helper.leadingZero(date.getSeconds()) + ': </i>';
  1541.             switch (messageType) {
  1542.                 case this.MESSAGE_ERROR:
  1543.                     message += '<span style="color: #F00;">' + text + '</span>';
  1544.                     break;
  1545.                 case this.MESSAGE_SUCCES:
  1546.                     message += '<span style="color: #0F0;">' + text + '</span>';
  1547.                     break;
  1548.                 case this.MESSAGE_WARNING:
  1549.                     message += '<span style="color:#FFA500;">' + text + '</span>';
  1550.                     break;
  1551.                 default:
  1552.                     message += '<span style="color: #FFF;">' + text + '</span>';
  1553.                     break;
  1554.             }
  1555.             twf.helper.messages.append('<li>' + message + '</li>');
  1556.             twf.helper.messages.scrollTop(twf.helper.messages[0].scrollHeight);
  1557.         },
  1558.     },
  1559.     html: {
  1560.         templatePopup: '<div id="template_popup" class="popup_box_container"> <div class="popup_box show" id="popup_box_quest" style="width: 700px;"> <div class="popup_box_content"> <a class="popup_box_close close_template_button" href="#">&nbsp;</a> <div style="width: 700px"> <div style="background: no-repeat url(' + " '/graphic/paladin_new.png' " + ');"> <h3 style="margin: 0 3px 5px 120px;">Create a template.</h3> <table align="right" style="margin-bottom: 5px;"> <tbody> <tr> <td class="quest-summary" style="width: 583px"> Create a template here to use for automatic farming. If you have enabled <em>Report Farming</em> under <em>Settings</em>, note that values here are minimum values. </td> </tr> </tbody> </table> <div class="quest-goal"> <table style="border:none;"> <tbody> <tr> <td valign="top"> <table class="vis" width="100%"> <tbody> <tr> <th>Infantry</th> </tr> <tr> <td class="nowrap "> <a href="#" class="unit_link" data-unit="spear"><img src="https://dsnl.innogamescdn.com/8.67/31807/graphic/unit/unit_spear.png" title="Spear" alt="" class=""></a> <input id="unit_input_spear" name="spear" type="text" style="width: 40px" tabindex="1" value="" class="unitsInput" data-all-count="39"></td> </tr> <tr> <td class="nowrap "> <a href="#" class="unit_link" data-unit="sword"><img src="https://dsnl.innogamescdn.com/8.67/31807/graphic/unit/unit_sword.png" title="Sword" alt="" class=""></a> <input id="unit_input_sword" name="sword" type="text" style="width: 40px" tabindex="2" value="" class="unitsInput" data-all-count="20"> </td> </tr> <tr> <td class="nowrap"> <a href="#" class="unit_link" data-unit="axe"><img src="https://dsnl.innogamescdn.com/8.67/31807/graphic/unit/unit_axe.png" title="Axe" alt="" class=""></a> <input id="unit_input_axe" name="axe" type="text" style="width: 40px" tabindex="3" value="" class="unitsInput" data-all-count="0"></td> </tr> <tr> <td class="nowrap"> <a href="#" class="unit_link" data-unit="archer"><img src="https://dsnl.innogamescdn.com/8.67/31807/graphic/unit/unit_archer.png" title="Archer" alt="" class=""></a> <input id="unit_input_archer" name="archer" type="text" style="width: 40px" tabindex="4" value="" class="unitsInput" data-all-count="0"> </td> </tr> </tbody> </table> </td> <td valign="top"> <table class="vis" width="100%"> <tbody> <tr> <th>Cavalry</th> </tr> <tr> <td class="nowrap "> <a href="#" class="unit_link" data-unit="spy"><img src="https://dsnl.innogamescdn.com/8.67/31807/graphic/unit/unit_spy.png" title="Spy" alt="" class=""></a> <input id="unit_input_spy" name="spy" type="text" style="width: 40px" tabindex="5" value="" class="unitsInput" data-all-count="4"></td> </tr> <tr> <td class="nowrap "> <a href="#" class="unit_link" data-unit="light"><img src="https://dsnl.innogamescdn.com/8.67/31807/graphic/unit/unit_light.png" title="LC" alt="" class=""></a> <input id="unit_input_light" name="light" type="text" style="width: 40px" tabindex="6" value="" class="unitsInput" data-all-count="13"> </td> </tr> <tr> <td class="nowrap "> <a href="#" class="unit_link" data-unit="marcher"><img src="https://dsnl.innogamescdn.com/8.67/31807/graphic/unit/unit_marcher.png" title="Marcher" alt="" class=""></a> <input id="unit_input_marcher" name="marcher" type="text" style="width: 40px" tabindex="7" value="" class="unitsInput" data-all-count="0"></td> </tr> <tr> <td class="nowrap "> <a href="#" class="unit_link" data-unit="heavy"><img src="https://dsnl.innogamescdn.com/8.67/31807/graphic/unit/unit_heavy.png" title="Heavy" alt="" class=""></a> <input id="unit_input_heavy" name="heavy" type="text" style="width: 40px" tabindex="8" value="" class="unitsInput" data-all-count="0"> </td> </tr> </tbody> </table> </td> <td valign="top"> <table class="vis" width="100%"> <tbody> <tr> <th>Siege</th> </tr> <tr> <td class="nowrap "> <a href="#" class="unit_link" data-unit="ram"><img src="https://dsnl.innogamescdn.com/8.67/31807/graphic/unit/unit_ram.png" title="Ram" alt="" class=""></a> <input id="unit_input_ram" name="ram" type="text" style="width: 40px" tabindex="9" value="" class="unitsInput" data-all-count="0"> </td> </tr> <tr> <td class="nowrap "> <a href="#" class="unit_link" data-unit="catapult"><img src="https://dsnl.innogamescdn.com/8.67/31807/graphic/unit/unit_catapult.png" title="Catapult" alt="" class=""></a> <input id="unit_input_catapult" name="catapult" type="text" style="width: 40px" tabindex="10" value="" class="unitsInput" data-all-count="0"></td> </tr> </tbody> </table> </td> <td valign="top"> <table class="vis" width="100%"> <tbody> <tr> <th>Other</th> </tr> <tr> <td class="nowrap "> <a href="#" class="unit_link" data-unit="knight"><img src="https://dsnl.innogamescdn.com/8.67/31807/graphic/unit/unit_knight.png" title="Knight" alt="" class=""></a> <input id="unit_input_knight" name="knight" type="text" style="width: 40px" tabindex="11" value="" class="unitsInput" data-all-count="0"> </td> </tr> <tr> <td class="nowrap "> <a href="#" class="unit_link" data-unit="snob"><img src="https://dsnl.innogamescdn.com/8.67/31807/graphic/unit/unit_snob.png" title="Snob" alt="" class=""></a> <input id="unit_input_snob" name="snob" type="text" style="width: 40px" tabindex="12" value="" class="unitsInput" data-all-count="0"> </td> </tr> </tbody> </table> </td> <td valign="top"> <table class="vis" width="100%"> <tbody> <tr> <th>Other settings</th> </tr> <tr> <td class="nowrap "> <input id="unit_input_name" name="name" type="text" style="width: 100%;" tabindex="13" value="" class="unitsInput" data-all-count="0" placeholder="Name"> </td> </tr> <tr> <td class="nowrap "> <input id="unit_input_coords" name="coords" type="text" style="width: 100%;" tabindex="13" value="" class="unitsInput" data-all-count="0"> </td> </tr> <tr> <td title="Send attacks regardless of amount of scouts available." class="nowrap"> <input id="ignore_scouts" name="ignore_scouts" type="checkbox" /> <label for="ignore_scouts">Ignore too little scouts</label> </td> </tr> </tbody> </table> <input type="hidden" id="unit_input_timestamp" value="" /> <input type="hidden" id="unit_input_position" value="" /> </td> </tr> </tbody> </table> </div> </div> <div align="center" style="padding: 10px;"> <a class="btn close_template_button" href="#">Close</a> <a class="btn" id="save_attack_template">Save and Close</a> </div> </div> </div> </div> <div class="fader"></div> </div>',
  1561.         css: '<style type="text/css">#settings_table td{ padding: 0 5px 0 5px; }#panel { background-color: #000000; border: 0 none; box-shadow: 5px 5px 10px #999999; border-bottom-left-radius: 15px; border-top-left-radius: 15px; -webkit-border-bottom-left-radius: 15px; -moz-border-radius-bottomleft: 15px; -webkit-border-top-left-radius: 15px; -moz-border-radius-topleft: 15px; float: right; color: #ddd; font-size: 10px; line-height: 1.5em; margin-right: 0%; opacity: 0.95; padding: 15px; padding-top: 1px; position: fixed; top: 60px; right: -315px; text-align: left; width: 300px; z-index: 12000; } #attackName { margin: 0 } #buttons {} #buttons button { width: 144px; margin: 0 2px; text-align: center;} #buttons input[type "checkbox"] { margin: 5px 2px 0 0; } #buttons p { width: 145px } #buttons label { width: 129px; display: inline-block } #unitTable { background: #000; width: 300px; } #unitTable.vis td { background: #000; } #attackListWrapper { height: 90px; width: 310px; overflow-y: auto; } #attackList { width: 300px; margin-top: 10px; } #attackList tr { height: 10px; } #attackList tr: nth-child(odd) { background-color: #c0c0c0; color: #0c0c0c; } #attackUnits { cursor: pointer; } #rAttackListWrapper { /*height: 80px;*/ width: 310px; overflow-y: auto; } #rAttackList { width: 300px; margin-top: 10px; } #rAttackList tr { height: 10px; color: #f00; font-wheight: bold; } #rAttackList tr.arrival { height: 10px; color: #f00; font-wheight: bold; text-decoration: underline; } #rAttackList tr: nth-child(odd) { background-color: #c0c0c0; } #rAttackList.timer { width: 50px; } #tack { margin: 0; cursor: pointer; } #loading { position: absolute; right: 0; bottom: 0; } #messages { list-style: none; width: 310px; height: 200px; overflow: auto; padding: 0 } #messages.note {} #messages.nor { color: #0f0; } #messages.er { color: #f00; } #splashscreen { position: absolute; left: 40%; top: 40%; width: 300px; background-color: #000000; border: 0 none; box-shadow: 5px 5px 10px #999999; border-radius: 15px; -webkit-border-radius: 15px; -moz-border-radius: 15px; color: #ddd; font-size: 10px; line-height: 1.5em; opacity: 0.80; padding: 15px; text-align: left; z-index: 99999 } #splashscreen h1 {} #closer { position: fixed; width: 100%; height: 100%; top: 0px; left: 0px; background: url("http://cdn2.tribalwars.net/graphic/index/grey-fade.png?01a9d"); z-index: 12000; } #captchaframe { position: absolute; left: 30%; top: 20%; width: 600px; background-color: #000000; border: 0 none; box-shadow: 5px 5px 10px #999999; border-radius: 15px; -webkit-border-radius: 15px; -moz-border-radius: 15px; color: #ddd; font-size: 10px; line-height: 1.5em; opacity: 0.80; padding: 15px; text-align: left; z-index: 99999 } #captchacloser { position: fixed; width: 100%; height: 100%; top: 0px; left: 0px; background: url("http://cdn2.tribalwars.net/graphic/index/grey-fade.png?01a9d"); z-index: 12000; } .timer {} .tooltip { display: none; position: absolute; left: -10px; background-color: #fff; color: #000; }</style>',
  1562.         panel: '<div id="panel"> <span id="tack"><img style="" src="https://openclipart.org/image/20px/svg_to_png/89059/394580430943859083405.png&disposition=attachment" class="off" height="20" /><img src="https://openclipart.org/image/20px/svg_to_png/33601/thumb%20tack%202%20plain.png&disposition=attachment" class="on" height="20" />TWF by Jari - Based on TWBot by TribalCarigan</span> <div id="newContent"> <div id="loading"><img src="graphic/throbber.gif" title="Loading something please wait..." alt="Loading something please wait..." /></div> <ul id="messages"> <li>Initialized layout</li> <li>Loading available troops</li> </ul> <div id="attackListWrapper"> <table id="attackList"></table> </div> <div id="rAttackListWrapper"> <table id="rAttackList"></table> </div> <h3 id="attackName"></h3> <table id="unitTable"> <tbody> <tr> <td valign="top"> <table class="vis" width="100%"> <tbody> <tr> <td id="attackUnits" class="nowrap"><img src="http://cdn2.tribalwars.net/graphic/command/attack.png?0019c" title="Attacked villages" alt="Attacked villages" class="" /><input id="attackedVillages" name="attackedVillages" type="text" style="width: 40px" tabindex="10" value="" class="unitsInput" /><i style="color: #000;" id="amount_of_attackedVillages">fetching...</i>&nbsp; </td> </tr> </tbody> </table> </td> </tr> <tr> <td valign="top"> <table class="vis" width="100%"> <tbody> <tr> <td style="color: black; width: 50%;" class="nowrap attacked_villages">Villages: <span id="attacked_villages">Fetching...</span></td> <td style="color: black; width: 50%;" class="nowrap reports_left">Reports: <span id="reports_left">Waiting for data.</span></td> </tr> </tbody> </table> </td> </tr> </tbody> </table> <div id="buttons"> <button class="btn btn-attack btn-disabled" id="attackButton" style="width: 296px; padding-right: 25px; padding-left: 25px;" disabled>Attack</button> <button class="btn btn-cancel btn-disabled" id="sAttackButton" style="display:none; width: 296px; padding-right: 25px; padding-left: 25px;" disabled>Cancel Attack</button> <button class="btn btn-recruit btn-disabled" id="cAttackButton" disabled>New Attack</button> <button class="btn btn-disabled" id="resetAttack" title="Reset attackcounter to the first village" disabled>Reset counter</button> <button class="btn btn-research btn-disabled" id="show_settings" disabled>Settings</button> <button class="btn btn-disabled" id="start_reports" disabled>Read reports</button> <button class="btn btn-cancel btn-disabled" id="stop_reports" style="display:none;" disabled>Stop reading</button> <button class="btn btn-pp btn-disabled" id="donate">Donate!</button> <button class="btn btn-disabled" id="wallbreaker">Wallbreaker</button> </div> </div> </div> ',
  1563.         settingsPopup: '<div id="settings_popup" class="popup_box_container"> <div class="popup_box show" id="popup_box_quest" style="width: 700px;"> <div class="popup_box_content"> <a class="popup_box_close close_settings_button" href="#">&nbsp;</a> <div style="width: 700px"> <div style="background: no-repeat url(' + " '/graphic/paladin_new.png' " + ');"> <h3 style="margin: 0 3px 5px 120px;">Global settings</h3> <table align="right" style="margin-bottom: 5px;"> <tbody> <tr> <td class="quest-summary" style="width: 583px"> <p> Modify various template aspecific settings here! Please note that using this bot is bannable and no combination of settings is allowed. That being said, the combination that is least likely to arouse suspicion consists of not checking any of the boxes (maybe except <em>Attack players</em>) in the General Settings column. Especially <em>Reset to first</em> and <em>Wait for troops</em> are very bot-like.</p> <p> With the <em>Poll Late</em> setting, you can delay refreshing the page after troops have arrived by a random amount. This is to make it seem less bot-like. </p> <p><strong>Report farmer</strong></p> <p>This tries to mimic the functionality of the farm assistant but better! Select whether you want to discount time and by what factor. This means that if it has been long since the last attack, we assume there are less resources than in the perfect case. For example, with a discount factor of 1.07, we assume that after 11 hours, only about half of the expected resources will actually be there.</p> </td> </tr> </tbody> </table> <div class="quest-goal"> <table style="border:none;" id="settings_table"> <tbody> <thead style="font-weight:bold;"> <tr> <td colspan="2">General</td> <td colspan="2">Botting</td> <td colspan="2">Reports</td> </tr> </thead> <tr> <td colspan="1" title="Reset to the first village when out of target villages"><label for="reset_to_first">Reset to first</label></td> <td colspan="1" title="Reset to the first village when out of target villages"><input type="checkbox" id="reset_to_first" name="reset_to_first" /></td> <td colspan="1" title="Seconds to refresh late"><label for="poll_late_seconds">Max seconds</label></td> <td colspan="1" title="Seconds to refresh late"><input style="width: 20px;" maxlength="3" type="text" id="poll_late_seconds" name="poll_late_seconds"/></td> <td colspan="1" title="Enable the report finder!"><label for="report_farmer">Report Finder</label></td> <td colspan="1" title="Enable the report finder!"><input type="checkbox" id="report_farmer" name="report_farmer" /></td> </tr> <tr> <td colspan="1" title="Wait for troops if they are unavailable"><label for="wait_for_troops">Wait for troops</label></td> <td colspan="1" title="Wait for troops if they are unavailable"><input type="checkbox" id="wait_for_troops" name="wait_for_troops" /></td> <td colspan="1" title="When waiting for troops, refresh too late sometimes"><label for="poll_late">Poll late</label></td> <td colspan="1" title="When waiting for troops, refresh too late sometimes"><input type="checkbox" id="poll_late" name="poll_late" /></td> <td colspan="1" title="When calculating expected resources, discount for time until haul"><label for="discount_time">Discount time</label></td> <td colspan="1" title="When calculating expected resources, discount for time until haul"><input type="checkbox" id="discount_time" name="discount_time" /></td> </tr> <tr> <td colspan="1" title="Attack player villages when farming"><label for="attack_players">Attack players</label></td> <td colspan="1" title="Attack player villages when farming"><input type="checkbox" id="attack_players" name="attack_players" /></td> <td></td> <td></td> <td colspan="1" title="Discount factor (between 1 and 2). Format: 1.07"><label for="discount_factor">Discount factor</label></td> <td colspan="1" title="Discount factor (between 1 and 2). Format: 1.07"><input style="width: 20px;" maxlength="4" type="text" id="discount_factor" name="discount_factor"/></td> </tr> <tr> <td colspan="1" title="Automatically stop after some minutes"><label for="autostop">Automatic stop</label></td> <td colspan="1" title="Automatically stop after some minutes"><input type="checkbox" id="autostop" name="autostop" /></td> <td></td> <td></td> <td colspan="1" title="Max age of a report for reading (h)"><label for="report_max_read_age">Max read age</label></td> <td colspan="1" title="Max age of a report for reading (h)"><input style="width: 20px;" maxlength="3" type="text" id="report_max_read_age" name="report_max_read_age"/></td> </tr> <tr> <td colspan="1" title="Minutes to run before stopping"><label for="autostop_minutes">Minutes to run</label></td> <td colspan="1" title="Minutes to run before stopping"><input style="width: 20px;" maxlength="3" type="text" id="autostop_minutes" name="autostop_minutes"/></td> <td></td> <td></td> <td colspan="1" title="Max age of a report for calculations (h)"><label for="report_max_use_age">Max calc age</label></td> <td colspan="1" title="Max age of a report for calculations (h)"><input style="width: 20px;" maxlength="3" type="text" id="report_max_use_age" name="report_max_read_age"/></td> <tr> </tr> </tbody> </table> </div> </div> <div align="center" style="padding: 10px;"> <a class="btn close_settings_button" href="#">Close</a> <a class="btn" id="save_settings">Save and Close</a> </div> </div> </div> </div> <div class="fader"></div> </div>'
  1564.     }
  1565. };
  1566.  
  1567. twf.init();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement