/// <summary> /// Inserts the given learning skill at the best possible position /// </summary> /// <param name="skill"></param> /// <param name="level"></param> public void InsertAtBestPosition(StaticSkill skill, int level) { using (SuspendingEvents()) { var entry = new PlanEntry(this, skill, level); // Look at prerequisites to search min and max insertion positions int minPosition = 0, maxPosition = m_items.Count; for (int i = 0; i < m_items.Count; i++) { var pEntry = m_items[i]; if (entry.IsDependentOf(pEntry)) { minPosition = Math.Max(minPosition, i + 1); } if (pEntry.IsDependentOf(entry)) { maxPosition = Math.Min(maxPosition, i); } } var bestTime = TimeSpan.MaxValue; var bestCandidatePosition = maxPosition; var scratchpad = new CharacterScratchpad(m_character); // We now search for the best insertion position for (int index = maxPosition; index >= minPosition; index--) { using (scratchpad.BeginTemporaryChanges()) { // Compute list's training time if the next item was inserted at index for (int i = 0; i <= m_items.Count; i++) { if (i < index) { scratchpad.Train(m_items[i]); } else if (i > index) { scratchpad.Train(m_items[i - 1]); } else { scratchpad.Train(entry); } } // Is it better with this index ? Then, we retain this as the best candidate if (bestTime > scratchpad.TrainingTime) { bestTime = scratchpad.TrainingTime; bestCandidatePosition = index; } } } // Insert at the best candidate position InsertCore(bestCandidatePosition, entry); } }
/// <summary> /// Gets the list of remapping results from a plan. /// </summary> /// <param name="plan"></param> /// <returns></returns> public static List <RemappingResult> GetResultsFromRemappingPoints(BasePlan plan) { var scratchpad = new CharacterScratchpad(plan.Character.After(plan.ChosenImplantSet)); var remappingList = new List <RemappingResult>(); var list = new List <ISkillLevel>(); RemappingResult remapping = null; // Scroll through the entries and split it into remappings foreach (var entry in plan) { // Ends the current remapping and start a new one if (entry.Remapping != null) { // Creates a new remapping remapping = new RemappingResult(entry.Remapping, scratchpad.Clone()); remappingList.Add(remapping); list = remapping.Skills; } // Add this skill to the training list scratchpad.Train(entry); list.Add(entry); } // Return return(remappingList); }
/// <summary> /// Updates the statistics of the entries in the same way the given character would train this plan. /// </summary> /// <param name="scratchpad"></param> /// <param name="applyRemappingPoints"></param> /// <param name="trainSkills">When true, the character will train every skill, increasing SP, etc.</param> public void UpdateStatistics(CharacterScratchpad scratchpad, bool applyRemappingPoints, bool trainSkills) { var scratchpadWithoutImplants = scratchpad.Clone(); scratchpadWithoutImplants.ClearImplants(); DateTime time = DateTime.Now; // Update the statistics foreach (var entry in m_items) { // Apply the remapping if (applyRemappingPoints && entry.Remapping != null && entry.Remapping.Status == RemappingPoint.PointStatus.UpToDate) { scratchpad.Remap(entry.Remapping); scratchpadWithoutImplants.Remap(entry.Remapping); } // Update entry's statistics entry.UpdateStatistics(scratchpad, scratchpadWithoutImplants, ref time); // Update the scratchpad if (trainSkills) { scratchpad.Train(entry.Skill, entry.Level); scratchpadWithoutImplants.Train(entry.Skill, entry.Level); } } }
/// <summary> /// Gets a character scratchpad representing this character after the provided skill levels trainings. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="trainings"></param> /// <returns></returns> public CharacterScratchpad After <T>(IEnumerable <T> trainings) where T : ISkillLevel { var scratchpad = new CharacterScratchpad(this); scratchpad.Train(trainings); return(scratchpad); }
/// <summary> /// Updates the statistics of the entries in the same way the given character would train this plan. /// </summary> /// <param name="scratchpad"></param> /// <param name="applyRemappingPoints"></param> /// <param name="trainSkills">When true, the character will train every skill, increasing SP, etc.</param> public void UpdateOldTrainingTimes(CharacterScratchpad scratchpad, bool applyRemappingPoints, bool trainSkills) { // Update the statistics foreach (var entry in m_items) { // Apply the remapping if (applyRemappingPoints && entry.Remapping != null && entry.Remapping.Status == RemappingPoint.PointStatus.UpToDate) { scratchpad.Remap(entry.Remapping); } // Update entry's statistics entry.UpdateOldTrainingTime(scratchpad); // Update the scratchpad if (trainSkills) { scratchpad.Train(entry.Skill, entry.Level); } } }
/// <summary> /// Compute the best possible attributes to fulfill the given trainings array /// </summary> /// <param name="skills"></param> /// <param name="baseScratchpad"></param> /// <param name="maxDuration"></param> /// <returns></returns> private static CharacterScratchpad Optimize <T>(IEnumerable <T> skills, CharacterScratchpad baseScratchpad, TimeSpan maxDuration) where T : ISkillLevel { CharacterScratchpad bestScratchpad = new CharacterScratchpad(baseScratchpad); CharacterScratchpad tempScratchpad = new CharacterScratchpad(baseScratchpad); TimeSpan baseTime = baseScratchpad.TrainingTime; TimeSpan bestTime = TimeSpan.MaxValue; int bestSkillCount = 0; // Now, we have the points to spend, let's perform all the // combinations (less than 11^4 = 14,641) for (int per = 0; per <= EveConstants.MaxRemappablePointsPerAttribute; per++) { // WIL int maxWillpower = EveConstants.SpareAttributePointsOnRemap - per; for (int will = 0; will <= maxWillpower && will <= EveConstants.MaxRemappablePointsPerAttribute; will++) { // INT int maxIntelligence = maxWillpower - will; for (int intell = 0; intell <= maxIntelligence && intell <= EveConstants.MaxRemappablePointsPerAttribute; intell++) { // MEM int maxMemory = maxIntelligence - intell; for (int mem = 0; mem <= maxMemory && mem <= EveConstants.MaxRemappablePointsPerAttribute; mem++) { // CHA int cha = maxMemory - mem; // Reject invalid combinations if (cha <= EveConstants.MaxRemappablePointsPerAttribute) { // Resets the scratchpad tempScratchpad.Reset(); // Set new attributes tempScratchpad.Memory.Base = mem + EveConstants.CharacterBaseAttributePoints; tempScratchpad.Charisma.Base = cha + EveConstants.CharacterBaseAttributePoints; tempScratchpad.Willpower.Base = will + EveConstants.CharacterBaseAttributePoints; tempScratchpad.Perception.Base = per + EveConstants.CharacterBaseAttributePoints; tempScratchpad.Intelligence.Base = intell + EveConstants.CharacterBaseAttributePoints; // Train skills int tempSkillCount = 0; foreach (var skill in skills) { tempSkillCount++; tempScratchpad.Train(skill); // Did it go over max duration ? if (tempScratchpad.TrainingTime - baseTime > maxDuration) { break; } // Did it go over the best time so far without training more skills ? if (tempSkillCount <= bestSkillCount && tempScratchpad.TrainingTime > bestTime) { break; } } // Did it manage to train more skills before the max duration, // or did it train the same number of skills in a lesser time ? if (tempSkillCount > bestSkillCount || (tempSkillCount == bestSkillCount && tempScratchpad.TrainingTime < bestTime)) { bestScratchpad.Reset(); bestScratchpad.Memory.Base = tempScratchpad.Memory.Base; bestScratchpad.Charisma.Base = tempScratchpad.Charisma.Base; bestScratchpad.Willpower.Base = tempScratchpad.Willpower.Base; bestScratchpad.Perception.Base = tempScratchpad.Perception.Base; bestScratchpad.Intelligence.Base = tempScratchpad.Intelligence.Base; bestTime = tempScratchpad.TrainingTime; bestSkillCount = tempSkillCount; } } } } } } // Return the best scratchpad found return(bestScratchpad); }
/// <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); }