/// <summary> /// Constructor /// </summary> /// <param name="plan">The plan to get suggestions for, it will be unchanged.</param> internal PlanSuggestions(Plan plan) { m_originalPlan = plan; m_plan = new PlanScratchpad(plan.Character, plan); var baseTime = m_plan.TotalTrainingTime; using (m_plan.SuspendingEvents()) { // Search for new benefits, in a recursive way (adding new skills change the opportunities for new improvements) while (true) { bool foundNewBenefits = false; foundNewBenefits |= CheckForTimeBenefit(GetLearningEntries(EveAttribute.Intelligence)); foundNewBenefits |= CheckForTimeBenefit(GetLearningEntries(EveAttribute.Perception)); foundNewBenefits |= CheckForTimeBenefit(GetLearningEntries(EveAttribute.Willpower)); foundNewBenefits |= CheckForTimeBenefit(GetLearningEntries(EveAttribute.Memory)); foundNewBenefits |= CheckForTimeBenefit(GetLearningEntries(EveAttribute.Charisma)); foundNewBenefits |= CheckForTimeBenefit(GetLearningEntries()); if (!foundNewBenefits) { break; } } } // Update the time benefit m_timeBenefit = baseTime - m_plan.TotalTrainingTime; }
/// <summary> /// Try to set the priority of the given entries and cancel if a conflict arises. /// </summary> /// <param name="entries">The list of entries to change priority of</param> /// <param name="priority">The new priority to set</param> /// <returns>True when successful, false when a conflict arised.</returns> public bool TrySetPriority(PlanScratchpad m_displayPlan, IEnumerable <PlanEntry> entries, int priority) { // Change priorities and make a backup Queue <int> oldPriorities = new Queue <int>(); foreach (var entry in entries) { oldPriorities.Enqueue(entry.Priority); entry.Priority = priority; } // We are rebuilding the plan with the new priorities in order to check them RebuildPlanFrom(m_displayPlan, true); // Check priorities if (FixPrioritiesOrder(false, false)) { // Priorities are OK we save them and return OnChanged(PlanChange.Notification); return(true); } // Failure, restore the priorities foreach (var entry in entries) { entry.Priority = oldPriorities.Dequeue(); } // We are rebuilding the plan from the old priorities RebuildPlanFrom(m_displayPlan, true); return(false); }
/// <summary> /// Check, among the sequence of skills, the number of them to add to get the best training time. /// Matched skills are then added to the plan and to the suggestions list. /// </summary> /// <param name="attribute"></param> /// <returns></returns> private bool CheckForTimeBenefit(IEnumerable <PlanEntry> entries) { // Gets the skills which are neither known nor planned var entriesToAdd = new List <PlanEntry>(); foreach (var entry in entries) { if (entry.CharacterSkill.Level < entry.Level && !m_plan.IsPlanned(entry.Skill, entry.Level)) { entriesToAdd.Add(entry); } } // Check which is the best level to train int entriesCount = 0; int bestEntriesCount = 0; var bestTime = m_plan.TotalTrainingTime; var testPlan = new PlanScratchpad(m_plan.Character, m_plan); foreach (var entry in entriesToAdd) { // Insert the skill at the best position, making sure its prerequisites are trained first. entriesCount++; testPlan.InsertAtBestPosition(entry.Skill, entry.Level); // Compare the new training time with the old one TimeSpan newTime = testPlan.TotalTrainingTime; if (newTime < bestTime) { bestTime = newTime; bestEntriesCount = entriesCount; } } // Add the suggested entries to the plan and the suggestions list entriesCount = 0; foreach (var entry in entriesToAdd) { // Stop when we reached the desired entries count if (entriesCount == bestEntriesCount) { break; } entriesCount++; m_items.Add(entry); m_plan.InsertAtBestPosition(entry.Skill, entry.Level); } // Returns true if new benefits have been found return(bestEntriesCount != 0); }
/// <summary> /// Removes a set of skill levels from this plan. /// </summary> /// <param name="skillsToRemove">The skill levels to remove.</param> /// <returns>An object allowing to perform and control the removal.</returns> public IPlanOperation TryRemoveSet <T>(IEnumerable <T> skillsToRemove) where T : ISkillLevel { var allEntriesToRemove = GetAllEntriesToRemove(skillsToRemove); // Creates a plan where the entries and their dependencies have been removed var freePlan = new PlanScratchpad(m_character); freePlan.RebuildPlanFrom(m_items); foreach (var entry in allEntriesToRemove) { freePlan.Remove(entry); } // Gather removables prerequisites now useless var removablePrerequisites = new List <PlanEntry>(); foreach (var entryToRemove in allEntriesToRemove) { foreach (var prereq in m_items) { // Skip if this skill was not dependent of the prereq. if (!entryToRemove.IsDependentOf(prereq)) { continue; } // Skip if still required if (freePlan.GetMinimumLevel(prereq.Skill) != 0) { continue; } // Add the entry if its type is Prerequisite var prereqEntry = freePlan.GetEntry(prereq.Skill, prereq.Level); if (prereqEntry == null) { continue; } // Adds it as a removable prerequisite if (prereqEntry.Type == PlanEntryType.Prerequisite) { removablePrerequisites.Add(prereqEntry); freePlan.Remove(prereqEntry); } } } // Create the operation return(new PlanOperation(this, skillsToRemove.Cast <ISkillLevel>(), allEntriesToRemove, removablePrerequisites)); }
/// <summary> /// Optimize the learning skills order /// </summary> /// <param name="baseEntries"></param> /// <param name="startSP"></param> /// <returns></returns> private PlanScratchpad OptimizeLearningSkills(IEnumerable <PlanEntry> baseEntries, int startSP) { var learningPlan = new PlanScratchpad(m_character); var entries = PrepareLearningSkillsInsertionQueue(baseEntries); // Insert all the entries in an optimal order while (entries.Count != 0) { // Pops the next level when there is one, quit otherwise PlanEntry entry = entries.Dequeue(); learningPlan.InsertAtBestPosition(entry.Skill, entry.Level); } return(learningPlan); }
/// <summary> /// Set the priorities by force, fixing conflicts when required. /// </summary> /// <param name="entries">The list of entries to change priority of</param> /// <param name="priority">The new priority to set</param> public void SetPriority(PlanScratchpad m_displayPlan, IEnumerable <PlanEntry> entries, int priority) { // Change priorities and determine how conflicts are going to be fixed bool loweringPriorities = true; foreach (var entry in entries) { loweringPriorities &= (priority > entry.Priority); entry.Priority = priority; } // We are rebuilding the plan with the new priorities RebuildPlanFrom(m_displayPlan, true); // Fix things up FixPrioritiesOrder(true, loweringPriorities); OnChanged(PlanChange.Notification); }
/// <summary> /// Exports the plan under a text format. /// </summary> /// <param name="planToExport"></param> /// <param name="settings"></param> /// <param name="exportActions"></param> /// <returns></returns> public static string ExportAsText(Plan planToExport, PlanExportSettings settings, ExportPlanEntryActions exportActions) { var plan = new PlanScratchpad(planToExport.Character, planToExport); plan.Sort(planToExport.SortingPreferences); plan.UpdateStatistics(); var builder = new StringBuilder(); var timeFormat = DescriptiveTextOptions.FullText | DescriptiveTextOptions.IncludeCommas | DescriptiveTextOptions.SpaceText; var character = (Character)plan.Character; // Initialize constants string lineFeed = Environment.NewLine; string boldStart = String.Empty; string boldEnd = String.Empty; switch (settings.Markup) { default: break; case MarkupType.Forum: boldStart = "[b]"; boldEnd = "[/b]"; break; case MarkupType.Html: lineFeed = String.Format(CultureInfo.InvariantCulture, "<br />{0}", Environment.NewLine); boldStart = "<b>"; boldEnd = "</b>"; break; } // Header if (settings.IncludeHeader) { builder.Append(boldStart); if (settings.ShoppingList) { builder.AppendFormat(CultureConstants.DefaultCulture, "Shopping list for {0}", character.Name); } else { builder.AppendFormat(CultureConstants.DefaultCulture, "Skill plan for {0}", character.Name); } builder.Append(boldEnd); builder.Append(lineFeed); builder.Append(lineFeed); } // Scroll through entries int index = 0; DateTime endTime = DateTime.Now; foreach (PlanEntry entry in plan) { // Remapping point if (!settings.ShoppingList && entry.Remapping != null) { builder.AppendFormat(CultureConstants.DefaultCulture, "***{0}***", entry.Remapping); builder.Append(lineFeed); } // Skip is we're only build a shopping list bool shoppingListCandidate = !(entry.CharacterSkill.IsKnown || entry.Level != 1 || entry.CharacterSkill.IsOwned); if (settings.ShoppingList && !shoppingListCandidate) continue; // Entry's index index++; if (settings.EntryNumber) builder.AppendFormat(CultureConstants.DefaultCulture, "{0}. ", index); // Name builder.Append(boldStart); if (settings.Markup.Equals(MarkupType.Html)) { if (!settings.ShoppingList) { builder.AppendFormat(CultureConstants.DefaultCulture, "<a href=\"\" onclick=\"CCPEVE.showInfo({0})\">", entry.Skill.ID); } else { builder.AppendFormat(CultureConstants.DefaultCulture, "<a href=\"\" onclick=\"CCPEVE.showMarketDetails({0})\">", entry.Skill.ID); } } builder.Append(entry.Skill.Name); if (settings.Markup == MarkupType.Html) builder.Append("</a>"); if (!settings.ShoppingList) builder.AppendFormat(CultureConstants.DefaultCulture, " {0}", Skill.GetRomanForInt(entry.Level)); builder.Append(boldEnd); // Training time if (settings.EntryTrainingTimes || settings.EntryStartDate || settings.EntryFinishDate || (settings.EntryCost && shoppingListCandidate)) { builder.Append(" ("); bool needComma = false; // Training time if (settings.EntryTrainingTimes) { needComma = true; builder.Append(entry.TrainingTime.ToDescriptiveText(timeFormat)); } // Training start date if (settings.EntryStartDate) { if (needComma) builder.Append("; "); needComma = true; builder.AppendFormat(CultureConstants.DefaultCulture, "Start: {0}", entry.StartTime); } // Training end date if (settings.EntryFinishDate) { if (needComma) builder.Append("; "); needComma = true; builder.AppendFormat(CultureConstants.DefaultCulture, "Finish: {0}", entry.EndTime); } // Skill cost if (settings.EntryCost && shoppingListCandidate) { if (needComma) builder.Append("; "); needComma = true; builder.AppendFormat(CultureConstants.DefaultCulture, "{0} ISK", entry.Skill.FormattedCost); } builder.Append(')'); } if (exportActions != null) exportActions(builder, entry, settings); builder.Append(lineFeed); // End time endTime = entry.EndTime; } // Footer if (settings.FooterCount || settings.FooterTotalTime || settings.FooterDate || settings.FooterCost) { builder.AppendLine(lineFeed); bool needComma = false; // Skills count if (settings.FooterCount) { builder.AppendFormat(CultureConstants.DefaultCulture, "{0}{1}{2}", boldStart, index, boldEnd); builder.Append((index == 1 ? " skill" : " skills")); needComma = true; } // Plan's training duration if (settings.FooterTotalTime) { if (needComma) builder.Append("; "); needComma = true; builder.AppendFormat(CultureConstants.DefaultCulture, "Total time: {0}{1}{2}", boldStart, plan.GetTotalTime(null, true).ToDescriptiveText(timeFormat), boldEnd); } // End training date if (settings.FooterDate) { if (needComma) builder.Append("; "); needComma = true; builder.AppendFormat(CultureConstants.DefaultCulture, "Completion: {0}{1}{2}", boldStart, endTime, boldEnd); } // Total books cost if (settings.FooterCost) { if (needComma) builder.Append("; "); needComma = true; string formattedIsk = String.Format(CultureConstants.TidyInteger, "{0:n}", plan.NotKnownSkillBooksCost); builder.AppendFormat(CultureConstants.DefaultCulture, "Cost: {0}{1}{2}", boldStart, formattedIsk, boldEnd); } // Warning about skill costs builder.Append(lineFeed); if (settings.FooterCost || settings.EntryCost) builder.Append("N.B. Skill costs are based on CCP's database and are indicative only"); } // Returns the text representation. return builder.ToString(); }
/// <summary> /// Exports the plan under a text format. /// </summary> /// <param name="planToExport"></param> /// <param name="settings"></param> /// <param name="exportActions"></param> /// <returns></returns> public static string ExportAsText(Plan planToExport, PlanExportSettings settings, ExportPlanEntryActions exportActions) { var plan = new PlanScratchpad(planToExport.Character, planToExport); plan.Sort(planToExport.SortingPreferences); plan.UpdateStatistics(); var builder = new StringBuilder(); var timeFormat = DescriptiveTextOptions.FullText | DescriptiveTextOptions.IncludeCommas | DescriptiveTextOptions.SpaceText; var character = (Character)plan.Character; // Initialize constants string lineFeed = Environment.NewLine; string boldStart = String.Empty; string boldEnd = String.Empty; switch (settings.Markup) { default: break; case MarkupType.Forum: boldStart = "[b]"; boldEnd = "[/b]"; break; case MarkupType.Html: lineFeed = String.Format(CultureInfo.InvariantCulture, "<br />{0}", Environment.NewLine); boldStart = "<b>"; boldEnd = "</b>"; break; } // Header if (settings.IncludeHeader) { builder.Append(boldStart); if (settings.ShoppingList) { builder.AppendFormat(CultureConstants.DefaultCulture, "Shopping list for {0}", character.Name); } else { builder.AppendFormat(CultureConstants.DefaultCulture, "Skill plan for {0}", character.Name); } builder.Append(boldEnd); builder.Append(lineFeed); builder.Append(lineFeed); } // Scroll through entries int index = 0; DateTime endTime = DateTime.Now; foreach (PlanEntry entry in plan) { // Remapping point if (!settings.ShoppingList && entry.Remapping != null) { builder.AppendFormat(CultureConstants.DefaultCulture, "***{0}***", entry.Remapping); builder.Append(lineFeed); } // Skip is we're only build a shopping list bool shoppingListCandidate = !(entry.CharacterSkill.IsKnown || entry.Level != 1 || entry.CharacterSkill.IsOwned); if (settings.ShoppingList && !shoppingListCandidate) { continue; } // Entry's index index++; if (settings.EntryNumber) { builder.AppendFormat(CultureConstants.DefaultCulture, "{0}. ", index); } // Name builder.Append(boldStart); if (settings.Markup.Equals(MarkupType.Html)) { if (!settings.ShoppingList) { builder.AppendFormat(CultureConstants.DefaultCulture, "<a href=\"\" onclick=\"CCPEVE.showInfo({0})\">", entry.Skill.ID); } else { builder.AppendFormat(CultureConstants.DefaultCulture, "<a href=\"\" onclick=\"CCPEVE.showMarketDetails({0})\">", entry.Skill.ID); } } builder.Append(entry.Skill.Name); if (settings.Markup == MarkupType.Html) { builder.Append("</a>"); } if (!settings.ShoppingList) { builder.AppendFormat(CultureConstants.DefaultCulture, " {0}", Skill.GetRomanForInt(entry.Level)); } builder.Append(boldEnd); // Training time if (settings.EntryTrainingTimes || settings.EntryStartDate || settings.EntryFinishDate || (settings.EntryCost && shoppingListCandidate)) { builder.Append(" ("); bool needComma = false; // Training time if (settings.EntryTrainingTimes) { needComma = true; builder.Append(entry.TrainingTime.ToDescriptiveText(timeFormat)); } // Training start date if (settings.EntryStartDate) { if (needComma) { builder.Append("; "); } needComma = true; builder.AppendFormat(CultureConstants.DefaultCulture, "Start: {0}", entry.StartTime); } // Training end date if (settings.EntryFinishDate) { if (needComma) { builder.Append("; "); } needComma = true; builder.AppendFormat(CultureConstants.DefaultCulture, "Finish: {0}", entry.EndTime); } // Skill cost if (settings.EntryCost && shoppingListCandidate) { if (needComma) { builder.Append("; "); } needComma = true; builder.AppendFormat(CultureConstants.DefaultCulture, "{0} ISK", entry.Skill.FormattedCost); } builder.Append(')'); } if (exportActions != null) { exportActions(builder, entry, settings); } builder.Append(lineFeed); // End time endTime = entry.EndTime; } // Footer if (settings.FooterCount || settings.FooterTotalTime || settings.FooterDate || settings.FooterCost) { builder.AppendLine(lineFeed); bool needComma = false; // Skills count if (settings.FooterCount) { builder.AppendFormat(CultureConstants.DefaultCulture, "{0}{1}{2}", boldStart, index, boldEnd); builder.Append((index == 1 ? " skill" : " skills")); needComma = true; } // Plan's training duration if (settings.FooterTotalTime) { if (needComma) { builder.Append("; "); } needComma = true; builder.AppendFormat(CultureConstants.DefaultCulture, "Total time: {0}{1}{2}", boldStart, plan.GetTotalTime(null, true).ToDescriptiveText(timeFormat), boldEnd); } // End training date if (settings.FooterDate) { if (needComma) { builder.Append("; "); } needComma = true; builder.AppendFormat(CultureConstants.DefaultCulture, "Completion: {0}{1}{2}", boldStart, endTime, boldEnd); } // Total books cost if (settings.FooterCost) { if (needComma) { builder.Append("; "); } needComma = true; string formattedIsk = String.Format(CultureConstants.TidyInteger, "{0:n}", plan.NotKnownSkillBooksCost); builder.AppendFormat(CultureConstants.DefaultCulture, "Cost: {0}{1}{2}", boldStart, formattedIsk, boldEnd); } // Warning about skill costs builder.Append(lineFeed); if (settings.FooterCost || settings.EntryCost) { builder.Append("N.B. Skill costs are based on CCP's database and are indicative only"); } } // Returns the text representation. return(builder.ToString()); }
/// <summary> /// Exports the plan under a text format. /// </summary> /// <param name="planToExport"></param> /// <param name="settings"></param> /// <returns></returns> public static string ExportAsText(Plan planToExport, PlanExportSettings settings) { var plan = new PlanScratchpad(planToExport.Character, planToExport); plan.Sort(planToExport.SortingPreferences); plan.UpdateStatistics(); var builder = new StringBuilder(); var timeFormat = DescriptiveTextOptions.FullText | DescriptiveTextOptions.IncludeCommas | DescriptiveTextOptions.SpaceText; var character = (Character)plan.Character; // Initialize constants string lineFeed = ""; string boldStart = ""; string boldEnd = ""; switch (settings.Markup) { default: break; case MarkupType.Forum: boldStart = "[b]"; boldEnd = "[/b]"; break; case MarkupType.Html: lineFeed = "<br />"; boldStart = "<b>"; boldEnd = "</b>"; break; } // Header if (settings.IncludeHeader) { builder.Append(boldStart); if (settings.ShoppingList) { builder.Append("Shopping list for ").Append(character.Name); } else { builder.Append("Skill plan for ").Append(character.Name); } builder.Append(boldEnd); builder.AppendLine(lineFeed); builder.AppendLine(lineFeed); } // Scroll through entries int index = 0; DateTime endTime = DateTime.Now; foreach (PlanEntry entry in plan) { // Remapping point if (!settings.ShoppingList && entry.Remapping != null) { builder.Append("***").Append(entry.Remapping.ToString()).Append("***"); builder.AppendLine(lineFeed); } // Skip is we're only build a shopping list bool shoppingListCandidate = !(entry.CharacterSkill.IsKnown || entry.Level != 1 || entry.CharacterSkill.IsOwned); if (settings.ShoppingList && !shoppingListCandidate) { continue; } // Entry's index index++; if (settings.EntryNumber) { builder.Append(index.ToString()).Append(". "); } // Name builder.Append(boldStart); if (settings.Markup == MarkupType.Html) { builder.Append("<a href=\"\" onclick=\"CCPEVE.showInfo(").Append(entry.Skill.ID.ToString()).Append(")\">"); } builder.Append(entry.Skill.Name); if (settings.Markup == MarkupType.Html) { builder.Append("</a>"); } if (!settings.ShoppingList) { builder.Append(' ').Append(Skill.GetRomanForInt(entry.Level)); } builder.Append(boldEnd); // Training time if (settings.EntryTrainingTimes || settings.EntryStartDate || settings.EntryFinishDate || (settings.EntryCost && shoppingListCandidate)) { builder.Append(" ("); bool needComma = false; // Training time if (settings.EntryTrainingTimes) { needComma = true; builder.Append(Skill.TimeSpanToDescriptiveText(entry.TrainingTime, timeFormat)); } // Training start date if (settings.EntryStartDate) { if (needComma) { builder.Append("; "); } needComma = true; builder.Append("Start: ").Append(entry.StartTime.ToString()); } // Training end date if (settings.EntryFinishDate) { if (needComma) { builder.Append("; "); } needComma = true; builder.Append("Finish: ").Append(entry.EndTime.ToString()); } // Skill cost if (settings.EntryCost && shoppingListCandidate) { if (needComma) { builder.Append("; "); } needComma = true; builder.Append(entry.Skill.FormattedCost + " ISK"); } builder.Append(')'); } builder.AppendLine(lineFeed); // End time endTime = entry.EndTime; } // Footer if (settings.FooterCount || settings.FooterTotalTime || settings.FooterDate || settings.FooterCost) { builder.AppendLine(lineFeed); bool needComma = false; // Skills count if (settings.FooterCount) { builder.Append(boldStart).Append(index.ToString()).Append(boldEnd); builder.Append((index == 1 ? " skill" : " skills")); needComma = true; } // Plan's training duration if (settings.FooterTotalTime) { if (needComma) { builder.Append("; "); } needComma = true; builder.Append("Total time: "); builder.Append(boldStart); builder.Append(Skill.TimeSpanToDescriptiveText(plan.GetTotalTime(null, true), timeFormat)); builder.Append(boldEnd); } // End training date if (settings.FooterDate) { if (needComma) { builder.Append("; "); } needComma = true; builder.Append("Completion: "); builder.Append(boldStart).Append(endTime.ToString()).Append(boldEnd); } // Total books cost if (settings.FooterCost) { if (needComma) { builder.Append("; "); } needComma = true; builder.Append("Cost: "); builder.Append(boldStart); builder.Append((plan.TotalBooksCost == 0 ? "0 ISK" : String.Format("{0:0,0,0} ISK", plan.TotalBooksCost))); builder.Append(boldEnd); } // Warning about skill costs builder.AppendLine(lineFeed); if (settings.FooterCost || settings.EntryCost) { builder.Append("N.B. Skill costs are based on CCP's database and are indicative only"); builder.AppendLine(lineFeed); } } // Returns the text representation. return(builder.ToString()); }
/// <summary> /// Performs the sort /// </summary> /// <param name="startSp"></param> /// <returns></returns> public IEnumerable <PlanEntry> Sort(int startSp) { int initialCount = m_entries.Count(); // Apply first pass (learning skills) // We split the entries into a head (learnings) and a tail (non-learnings) PlanScratchpad headPlan = new PlanScratchpad(m_character); List <PlanEntry> tailEntries = new List <PlanEntry>(); if (m_learningSkillsFirst) { tailEntries.AddRange(m_entries.Where(x => x.Skill.LearningClass == LearningClass.None)); var learningSkills = m_entries.Where(x => x.Skill.LearningClass != LearningClass.None); headPlan = OptimizeLearningSkills(learningSkills, startSp); } else { tailEntries.AddRange(m_entries); } // Apply second pass (priorities grouping) // We split the tail into multiple tail groups List <PlanScratchpad> tailEntryPlans = new List <PlanScratchpad>(); var scratchpad = new CharacterScratchpad(m_character); scratchpad.Train(headPlan); if (m_groupByPriority) { foreach (var group in tailEntries.GroupBy(x => x.Priority)) { tailEntryPlans.Add(new PlanScratchpad(scratchpad, group)); } } else { tailEntryPlans.Add(new PlanScratchpad(scratchpad, tailEntries)); } // Apply third pass (sorts) // We sort every tail group, and merge them once they're sorted. List <PlanEntry> list = new List <PlanEntry>(); list.AddRange(headPlan); foreach (var tailPlan in tailEntryPlans) { tailPlan.UpdateStatistics(scratchpad, false, false); tailPlan.SimpleSort(m_sort, m_reverseOrder); list.AddRange(tailPlan); } // This is actually what GroupByPriority should do if (m_groupByPriority) { list.StableSort(PlanSorter.CompareByPriority); } // Fix prerequisites order FixPrerequisitesOrder(list); // Check we didn't mess up anything if (initialCount != list.Count) { throw new UnauthorizedAccessException("The sort algorithm messed up and deleted items"); } // Return return(list); }