/// <summary> /// If there are enough resources, return TimeSpan(0) /// Otherwise calculate how long it will take to get enough resources and transit res from /// main village, if we have that enabled. Return the one that takes less time. /// DateTime for usage in nextExecution time /// </summary> /// <param name="acc">Account</param> /// <param name="vill">(target) Village</param> /// <param name="res">Resources required</param> /// <returns></returns> public static DateTime EnoughResourcesOrTransit(Account acc, Village vill, Resources res) { DateTime enoughRes = DateTime.Now.Add(TimeHelper.EnoughResToUpgrade(vill, res)); //We have enough resources, return DateTime.Now if (enoughRes < DateTime.Now.AddMilliseconds(1)) { return(DateTime.Now); } //Not enough resources, send resources //TODO: this isn't working like supposed to. If we have small transport time, (below 2min, return 2min, so bot has time to send res DateTime resTransit = MarketHelper.TransitResources(acc, vill); return(enoughRes < resTransit ? enoughRes : resTransit); }
/// <summary> /// Finds out which transits will be over soonest and returns the datetime. /// </summary> /// <param name="transitsO"></param> /// <returns></returns> public static DateTime SoonestAvailableMerchants(Account acc, Village vill1, Village vill2, List <MerchantsUnderWay> transitsO) { var transits = transitsO .Where(x => x.Transit == TransitType.Outgoin || x.Transit == TransitType.Returning) .ToList(); var ret = DateTime.MaxValue; foreach (var transit in transits) { DateTime time = DateTime.MaxValue; switch (transit.Transit) { case TransitType.Outgoin: var oneTransitTime = MarketHelper.CalculateTransitTime(acc, vill1, vill2); time = transit.Arrival.Add(oneTransitTime); if (transit.RepeatTimes > 1) { time += TimeHelper.MultiplyTimespan( oneTransitTime, (2 * (transit.RepeatTimes - 1)) ); } break; case TransitType.Returning: time = transit.Arrival; if (transit.RepeatTimes > 1) { time += TimeHelper.MultiplyTimespan( MarketHelper.CalculateTransitTime(acc, vill1, vill2), (2 * (transit.RepeatTimes - 1)) ); } break; } if (ret > time) { ret = time; } } return(ret); }
public static void StartAccountTasks(Account acc) { // If we don't know server speed, go and get it if (acc.AccInfo.ServerSpeed == 0) { TaskExecutor.AddTaskIfNotExists(acc, new GetServerSpeed() { ExecuteAt = DateTime.MinValue.AddHours(2) }); } if (acc.AccInfo.MapSize == 0) { TaskExecutor.AddTaskIfNotExists(acc, new GetMapSize() { ExecuteAt = DateTime.MinValue.AddHours(2) }); } //FL if (acc.Farming.Enabled) { TaskExecutor.AddTaskIfNotExists(acc, new SendFLs() { ExecuteAt = DateTime.Now }); } //research / improve / train troops foreach (var vill in acc.Villages) { //if (vill.Troops.Researched.Count == 0) TaskExecutor.AddTask(acc, new UpdateTroops() { ExecuteAt = DateTime.Now, vill = vill }); TroopsHelper.ReStartResearchAndImprovement(acc, vill); if (!TroopsHelper.EverythingFilled(acc, vill)) { TroopsHelper.ReStartTroopTraining(acc, vill); } BuildingHelper.ReStartBuilding(acc, vill); BuildingHelper.ReStartDemolishing(acc, vill); MarketHelper.ReStartSendingToMain(acc, vill); //todo } }
/// <summary> /// Used by BotTasks to insert resources/coordinates into the page. /// </summary> /// <param name="acc">Account</param> /// <param name="resources">Target resources</param> /// <param name="coordinates">Target coordinates</param> /// <returns>Time it will take for transit to complete</returns> public static async Task <TimeSpan> MarketSendResource(Account acc, long[] resources, Village targetVillage, BotTask botTask) { var times = 1; if (acc.AccInfo.GoldClub ?? false) { times = 3; } else if (acc.AccInfo.PlusAccount) { times = 2; } // No resources to send if (resources.Sum() == 0) { return(TimeSpan.Zero); } var sendRes = resources.Select(x => x / times).ToArray(); //round the resources that we want to send, so it looks less like a bot (var merchantsCapacity, var merchantsNum) = MarketHelper.ParseMerchantsInfo(acc.Wb.Html); // We don't have any merchants. if (merchantsNum == 0) { //Parse currently ongoing transits var transits = MarketParser.ParseTransits(acc.Wb.Html); var activeVill = acc.Villages.FirstOrDefault(x => x.Active); // Could also just pass that in params var nextTry = SoonestAvailableMerchants(acc, activeVill, targetVillage, transits); if (nextTry != DateTime.MaxValue) { nextTry = nextTry.AddSeconds(5); } botTask.NextExecute = nextTry; // Just return something, will get overwritten anyways. return(new TimeSpan((int)(nextTry - DateTime.Now).TotalHours + 1, 0, 0)); } var maxRes = merchantsCapacity * times; var allRes = resources.Sum(); if (allRes > maxRes) { // We don't have enough merchants to transit all the resources. Divide all resources by some divider. var resDivider = (float)allRes / maxRes; float[] resFloat = sendRes.Select(x => x / resDivider).ToArray(); sendRes = resFloat.Select(x => (long)Math.Floor(x)).ToArray(); } var wb = acc.Wb.Driver; for (int i = 0; i < 4; i++) { // To avoid exception devide by zero if (50 <= sendRes[i]) { //round the number to about -1%, for rounder numbers var digits = Math.Ceiling(Math.Log10(sendRes[i])); var remainder = sendRes[i] % (long)Math.Pow(10, digits - 2); sendRes[i] -= remainder; await DriverHelper.WriteById(acc, "r" + (i + 1), sendRes[i]); } await Task.Delay(AccountHelper.Delay() / 5); } // Input coordinates await DriverHelper.WriteCoordinates(acc, targetVillage.Coordinates); //Select x2/x3 if (times != 1) { wb.ExecuteScript($"document.getElementById('x2').value='{times}'"); await Task.Delay(AccountHelper.Delay() / 5); } await DriverHelper.ClickById(acc, "enabledButton"); var durNode = acc.Wb.Html.GetElementbyId("target_validate"); if (durNode == null && acc.Wb.Html.GetElementbyId("prepareError") != null) { // Error "Abuse! You have not enough resources." is displayed. } //get duration of transit var dur = durNode.Descendants("td").ToList()[3].InnerText.Replace("\t", "").Replace("\n", ""); // Will NOT trigger a page reload! Thus we should await some time before continuing. await DriverHelper.ClickById(acc, "enabledButton"); targetVillage.Market.LastTransit = DateTime.Now; var duration = TimeParser.ParseDuration(dur); return(TimeSpan.FromTicks(duration.Ticks * (times * 2 - 1))); }
/// <summary> /// If there are enough resources, return TimeSpan(0) /// Otherwise calculate how long it will take to get enough resources and transit res from /// main village, if we have that enabled. Return the one that takes less time. /// DateTime for usage in nextExecution time /// </summary> /// <param name="acc">Account</param> /// <param name="vill">(target) Village</param> /// <param name="requiredRes">Resources required</param> /// <param name="task">Bot task that doesn't have enough resources</param> /// <param name="buildingTask">Potential building task</param> /// <returns>When next village update should occur</returns> private static DateTime?NewUnfinishedTask(Account acc, Village vill, Resources requiredRes, BotTask task, BuildingTask buildingTask = null) { var stillNeededRes = SubtractResources(requiredRes.ToArray(), vill.Res.Stored.Resources.ToArray(), true); // Whether we have enough resources. This should already be checked before calling this method! if (IsZeroResources(stillNeededRes)) { ResSpendingHelper.AddUnfinishedTask(vill, task, requiredRes); return(DateTime.Now); } acc.Wb.Log($"Not enough resources for the task {task.GetName()}! Needed {requiredRes}. Bot will try finish the task later"); if (IsStorageTooLow(acc, vill, requiredRes)) { acc.Wb.Log($"Storage is too low."); ResSpendingHelper.AddUnfinishedTask(vill, task, requiredRes); return(null); } // Try to use hero resources first if (vill.Settings.UseHeroRes && acc.AccInfo.ServerVersion == ServerVersionEnum.T4_5) // Only T4.5 has resources in hero inv { var heroRes = HeroHelper.GetHeroResources(acc); // If we have some hero resources, we should use those first if (!IsZeroResources(heroRes)) { var heroEquipTask = UseHeroResources(acc, vill, ref stillNeededRes, heroRes, buildingTask); // If we have enough hero res for our task, execute the task // right after hero equip finishes if (IsZeroResources(SubtractResources(stillNeededRes, heroRes, true))) { heroEquipTask.NextTask = task; return(null); } } } ResSpendingHelper.AddUnfinishedTask(vill, task, requiredRes); // When will we have enough resources from production DateTime enoughRes = TimeHelper.EnoughResToUpgrade(vill, stillNeededRes); var mainVill = AccountHelper.GetMainVillage(acc); if (mainVill == vill) { return(enoughRes); } DateTime resTransit = MarketHelper.TransitResourcesFromMain(acc, vill); if (resTransit < enoughRes) { enoughRes = resTransit; } if (enoughRes < DateTime.Now) { return(DateTime.Now); } return(enoughRes); }
public static void StartAccountTasks(Account acc) { // Get the server info (on first running the account) if (acc.AccInfo.ServerSpeed == 0 || acc.AccInfo.MapSize == 0) { TaskExecutor.AddTaskIfNotExists(acc, new GetServerInfo() { ExecuteAt = DateTime.MinValue.AddHours(2) }); } if (acc.AccInfo.Tribe == null) { TaskExecutor.AddTaskIfNotExists(acc, new GetTribe() { ExecuteAt = DateTime.MinValue.AddHours(3) }); } //FL if (acc.Farming.Enabled) { TaskExecutor.AddTaskIfNotExists(acc, new SendFLs() { ExecuteAt = DateTime.Now }); } // Bot sleep TaskExecutor.AddTaskIfNotExists(acc, new Sleep() { ExecuteAt = DateTime.Now + TimeHelper.GetWorkTime(acc), AutoSleep = true }); // Access change var nextAccessChange = TimeHelper.GetNextProxyChange(acc); if (nextAccessChange != TimeSpan.MaxValue) { TaskExecutor.AddTaskIfNotExists(acc, new ChangeAccess() { ExecuteAt = DateTime.Now + nextAccessChange }); } //research / improve / train troops foreach (var vill in acc.Villages) { //if (vill.Troops.Researched.Count == 0) TaskExecutor.AddTask(acc, new UpdateTroops() { ExecuteAt = DateTime.Now, vill = vill }); TroopsHelper.ReStartResearchAndImprovement(acc, vill); TroopsHelper.ReStartTroopTraining(acc, vill); BuildingHelper.ReStartBuilding(acc, vill); BuildingHelper.ReStartDemolishing(acc, vill); MarketHelper.ReStartSendingToMain(acc, vill); ReStartCelebration(acc, vill); VillageHelper.SetNextRefresh(acc, vill); if (vill.FarmingNonGold.OasisFarmingEnabled) { TaskExecutor.AddTaskIfNotExistInVillage(acc, vill, new AttackOasis() { Vill = vill }); } // Remove in later updates! if (vill.Settings.RefreshMin == 0) { vill.Settings.RefreshMin = 30; } if (vill.Settings.RefreshMax == 0) { vill.Settings.RefreshMax = 60; } } // Remove in later updates! if (acc.Hero.Settings.MinUpdate == 0) { acc.Hero.Settings.MinUpdate = 40; } if (acc.Hero.Settings.MaxUpdate == 0) { acc.Hero.Settings.MaxUpdate = 80; } // Hero update info if (acc.Hero.Settings.AutoRefreshInfo) { Random ran = new Random(); TaskExecutor.AddTask(acc, new HeroUpdateInfo() { ExecuteAt = DateTime.Now.AddMinutes(ran.Next(40, 80)), Priority = Tasks.BotTask.TaskPriority.Low }); } }
/// <summary> /// Used by BotTasks to insert resources/coordinates into the page. /// </summary> /// <param name="acc">Account</param> /// <param name="resources">Target resources</param> /// <param name="coordinates">Target coordinates</param> /// <returns>Time it will take for transit to complete</returns> public static async Task <TimeSpan> MarketSendResource(Account acc, long[] resources, Village targetVillage, BotTask botTask) { var times = 1; if (acc.AccInfo.GoldClub ?? false) { times = 3; } else if (acc.AccInfo.PlusAccount) { times = 2; } var sendRes = resources.Select(x => x / times).ToArray(); //round the resources that we want to send, so it looks less like a bot //TODO: check if we have enough merchants (var merchantsCapacity, var merchantsNum) = MarketHelper.ParseMerchantsInfo(acc.Wb.Html); // We don't have any merchants. if (merchantsNum == 0) { //Parse currently ongoing transits var transits = MarketParser.ParseTransits(acc.Wb.Html); var activeVill = acc.Villages.FirstOrDefault(x => x.Active); // Could also just pass that in params var nextTry = SoonestAvailableMerchants(acc, activeVill, targetVillage, transits); botTask.NextExecute = nextTry.AddSeconds(5); // Just return something, will get overwritten anyways. return(new TimeSpan((int)(nextTry - DateTime.Now).TotalHours + 1, 0, 0)); } var maxRes = merchantsCapacity * times; var allRes = resources.Sum(); if (allRes > maxRes) { // We don't have enough merchants to transit all the resources. Divide all resources by some divider. var resDivider = (float)allRes / maxRes; float[] resFloat = sendRes.Select(x => x / resDivider).ToArray(); sendRes = resFloat.Select(x => (long)Math.Floor(x)).ToArray(); } var wb = acc.Wb.Driver; for (int i = 0; i < 4; i++) { // To avoid exception devide by zero if (sendRes[i] != 0) { //round the number to about -1%, for rounder numbers var digits = Math.Ceiling(Math.Log10(sendRes[i])); var remainder = sendRes[i] % (long)Math.Pow(10, digits - 2); sendRes[i] -= remainder; } wb.ExecuteScript($"document.getElementById('r{i + 1}').value='{sendRes[i]}'"); await Task.Delay(AccountHelper.Delay() / 5); } wb.ExecuteScript($"document.getElementById('xCoordInput').value='{targetVillage.Coordinates.x}'"); await Task.Delay(AccountHelper.Delay() / 5); wb.ExecuteScript($"document.getElementById('yCoordInput').value='{targetVillage.Coordinates.y}'"); await Task.Delay(AccountHelper.Delay() / 5); //Select x2/x3 if (times != 1) { wb.ExecuteScript($"document.getElementById('x2').value='{times}'"); await Task.Delay(AccountHelper.Delay() / 5); } // Some bot protection here I guess. Just remove the class of the DOM. var script = @" var button = document.getElementById('enabledButton'); button.click(); "; wb.ExecuteScript(script); //Prepare //update htmlDoc, parse duration, TODO: maybe some other method to wait until the page is loaded? HtmlNode durNode = null; do { await Task.Delay(AccountHelper.Delay()); HtmlDocument html2 = new HtmlDocument(); html2.LoadHtml(wb.PageSource); durNode = html2.GetElementbyId("target_validate"); }while (durNode == null); //get duration of transit var dur = durNode.Descendants("td").ToList()[3].InnerText.Replace("\t", "").Replace("\n", ""); // Will NOT trigger a page reload! Thus we should await some time before continuing. wb.ExecuteScript("document.getElementById('enabledButton').click()"); //SendRes await Task.Delay(AccountHelper.Delay() * 2); var duration = TimeParser.ParseDuration(dur); return(TimeSpan.FromTicks(duration.Ticks * (times * 2 - 1))); }
/// <summary> /// Will be called before each task to update resources/msg?,villages,quests,hero health, adventures num, gold/silver /// </summary> /// <param name="acc">Account</param> /// <returns>True if successful, false if error</returns> private static bool PreTaskRefresh(Account acc) { var html = acc.Wb.Html; try { //check & update dorf1/dorf2 if (!UpdateAccountObject.UpdateVillages(html, acc)) { return(false); //Web browser not initiali } var activeVill = acc.Villages.FirstOrDefault(x => x.Active); //update dorf1/dorf2 if (acc.Wb.CurrentUrl.Contains("dorf1")) { UpdateDorf1Info(acc); } else if (acc.Wb.CurrentUrl.Contains("dorf2")) { UpdateDorf2Info(acc); } acc.AccInfo.CulturePoints = RightBarParser.GetCulurePoints(html, acc.AccInfo.ServerVersion); var villExpansionReady = acc.Villages.FirstOrDefault(x => x.Expansion.ExpensionAvailable); if (acc.AccInfo.CulturePoints.MaxVillages > acc.AccInfo.CulturePoints.VillageCount && villExpansionReady != null) { villExpansionReady.Expansion.ExpensionAvailable = false; TaskExecutor.AddTaskIfNotExists(acc, new SendSettlers() { ExecuteAt = DateTime.Now, vill = villExpansionReady }); } acc.AccInfo.Tribe = LeftBarParser.GetAccountTribe(acc, html); acc.Quests = RightBarParser.GetQuests(html); var goldSilver = RightBarParser.GetGoldAndSilver(html, acc.AccInfo.ServerVersion); acc.AccInfo.Gold = goldSilver[0]; acc.AccInfo.Silver = goldSilver[1]; acc.AccInfo.PlusAccount = RightBarParser.HasPlusAccount(html, acc.AccInfo.ServerVersion); //Check reports/msg count if (MsgParser.UnreadMessages(html, acc.AccInfo.ServerVersion) > 0 && !acc.Wb.CurrentUrl.Contains("messages.php") && !IsTaskOnQueue(acc, typeof(ReadMessage))) { TaskExecutor.AddTask(acc, new ReadMessage() { ExecuteAt = DateTime.Now.AddMilliseconds(AccountHelper.Delay() * 30) }); } //update loyalty of village activeVill.Res.FreeCrop = RightBarParser.GetFreeCrop(html); activeVill.Res.Capacity = ResourceParser.GetResourceCapacity(html, acc.AccInfo.ServerVersion); activeVill.Res.Stored = ResourceParser.GetResources(html); float ratio = (float)activeVill.Res.Stored.Resources.Crop / activeVill.Res.Capacity.GranaryCapacity; if (ratio >= 0.99 && acc.AccInfo.Gold >= 3 && activeVill.Market.Npc.Enabled && (activeVill.Market.Npc.NpcIfOverflow || !MarketHelper.NpcWillOverflow(activeVill))) { //npc crop! TaskExecutor.AddTaskIfNotExistInVillage(acc, activeVill, new NPC() { ExecuteAt = DateTime.MinValue, vill = activeVill }); } if (acc.Settings.AutoActivateProductionBoost && CheckProductionBoost(acc)) { TaskExecutor.AddTask(acc, new TTWarsPlusAndBoost() { ExecuteAt = DateTime.Now.AddSeconds(1) }); } acc.Hero.AdventureNum = HeroParser.GetAdventureNum(html, acc.AccInfo.ServerVersion); acc.Hero.Status = HeroParser.HeroStatus(html, acc.AccInfo.ServerVersion); acc.Hero.HeroInfo.Health = HeroParser.GetHeroHealth(html, acc.AccInfo.ServerVersion); bool heroReady = (acc.Hero.HeroInfo.Health > acc.Hero.Settings.MinHealth && acc.Hero.Settings.AutoSendToAdventure && acc.Hero.Status == Hero.StatusEnum.Home && acc.Hero.NextHeroSend < DateTime.Now); // Update adventures if (heroReady && (acc.Hero.AdventureNum != acc.Hero.Adventures.Count() || HeroHelper.AdventureInRange(acc))) //update adventures { AddTaskIfNotExists(acc, new StartAdventure() { ExecuteAt = DateTime.Now.AddSeconds(10) }); } if (acc.Hero.AdventureNum == 0 && acc.Hero.Settings.BuyAdventures) //for UNL servers, buy adventures { AddTaskIfNotExists(acc, new TTWarsBuyAdventure() { ExecuteAt = DateTime.Now.AddSeconds(5) }); } if (acc.Hero.Status == Hero.StatusEnum.Dead && acc.Hero.Settings.AutoReviveHero) //if hero is dead, revive him { AddTaskIfNotExists(acc, new ReviveHero() { ExecuteAt = DateTime.Now.AddSeconds(5), vill = AccountHelper.GetHeroReviveVillage(acc) }); } if (HeroParser.LeveledUp(html) && acc.Hero.Settings.AutoSetPoints) { AddTaskIfNotExists(acc, new HeroSetPoints() { ExecuteAt = DateTime.Now }); } return(true); } catch (Exception e) { Console.WriteLine("Error in PreTask " + e.Message + "\n\nStack Trace: " + e.StackTrace + "\n-----------------------"); return(false); } }