/// <summary> /// Gets the time require to train the given skills and their prerequisites /// </summary> /// <param name="trainings">A sequence of pairs of skills and the target levels.</param> /// <returns></returns> public TimeSpan GetTrainingTimeToMultipleSkills <T>(IEnumerable <T> trainings) where T : ISkillLevel { CharacterScratchpad scratchpad = this.After(trainings); return(scratchpad.TrainingTime); }
/// <summary> /// Generate a trainings array from the skills already know by a character. /// </summary> /// <param name="character">The character.</param> /// <param name="plan">The plan.</param> /// <returns></returns> public static RemappingResult OptimizeFromCharacter(Character character, BasePlan plan) { // Create a character without any skill var scratchpad = new CharacterScratchpad(character.After(plan.ChosenImplantSet)); scratchpad.ClearSkills(); // Create a new plan var newPlan = new Plan(scratchpad); // Add all trained skill levels that the character has trained so far foreach (var skill in character.Skills) { newPlan.PlanTo(skill, skill.Level); } // Create a new remapping var remapping = new RemappingResult(scratchpad); // Add those skills to the remapping foreach (var entry in newPlan) { remapping.Skills.Add(entry); } // Optimize remapping.Optimize(TimeSpan.MaxValue); return(remapping); }
/// <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> /// 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 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> /// Gets a character scratchpad representing this character after a switch to the provided implant set. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="trainings"></param> /// <returns></returns> public CharacterScratchpad After(ImplantSet set) { var scratchpad = new CharacterScratchpad(this); for (int i = 0; i < 5; i++) { var attribute = (EveAttribute)i; scratchpad[attribute].ImplantBonus = set[attribute].Bonus; } return(scratchpad); }
/// <summary> /// Gets the total training time for this plan, using the given scratchpad. /// </summary> /// <param name="scratchpad">The scratchpad to use for the computation, may be null.</param> /// <param name="applyRemappingPoints"></param> /// <returns></returns> public TimeSpan GetTotalTime(CharacterScratchpad scratchpad, bool applyRemappingPoints) { // No scratchpad ? Let's create one if (scratchpad == null) { scratchpad = new CharacterScratchpad(m_character); } // Train entries TimeSpan time = TimeSpan.Zero; scratchpad.TrainEntries(m_items, applyRemappingPoints); return(scratchpad.TrainingTime - time); }
/// <summary> /// Resets this scratchpad using the provided scratchpad. /// </summary> /// <param name="scratchpad"></param> public void Reset(CharacterScratchpad scratchpad) { m_totalSP = scratchpad.m_totalSP; m_trainingTime = scratchpad.m_trainingTime; m_trainedSkills.Clear(); m_trainedSkills.AddRange(scratchpad.m_trainedSkills); for (int i = 0; i < m_attributes.Length; i++) { m_attributes[i].Reset(scratchpad.m_attributes[i]); } scratchpad.m_skillSP.CopyTo(m_skillSP, 0); scratchpad.m_skillLevels.CopyTo(m_skillLevels, 0); }
/// <summary> /// Gets a string representation of the attribute /// </summary> /// <param name="attrib"></param> /// <param name="oldScratchpad"></param> /// <param name="newScratchpad"></param> /// <returns></returns> public static string GetStringForAttribute(EveAttribute attrib, CharacterScratchpad oldScratchpad, CharacterScratchpad newScratchpad) { var bonusDifference = newScratchpad[attrib].Base - oldScratchpad[attrib].Base; if (bonusDifference == 0) { return(newScratchpad[attrib].ToString("%N (-) = %e = (%b + %s + %i) * %f")); } else if (bonusDifference > 0) { return(newScratchpad[attrib].ToString("%N (+" + bonusDifference.ToString() + ") = %e = (%b + %s + %i) * %f")); } else { return(newScratchpad[attrib].ToString("%N (" + bonusDifference.ToString() + ") = %e = (%b + %s + %i) * %f")); } }
/// <summary> /// Gets a string representation of the attribute /// </summary> /// <param name="attrib"></param> /// <param name="oldScratchpad"></param> /// <param name="newScratchpad"></param> /// <returns></returns> public static string GetStringForAttribute(EveAttribute attrib, CharacterScratchpad oldScratchpad, CharacterScratchpad newScratchpad) { int bonusDifference = newScratchpad[attrib].Base - oldScratchpad[attrib].Base; if (bonusDifference == 0) { return(newScratchpad[attrib].ToString("%N (0) = %e = (%B + %r + %i)")); } else if (bonusDifference > 0) { return(newScratchpad[attrib].ToString(String.Format("%N (+{0}) = %e = (%B + %r + %i)", bonusDifference))); } else { return(newScratchpad[attrib].ToString(String.Format("%N ({0}) = %e = (%B + %r + %i)", bonusDifference))); } }
/// <summary> /// Performs the sort. /// </summary> /// <returns></returns> public IEnumerable <PlanEntry> Sort() { int initialCount = m_entries.Count(); // Apply first pass (priorities grouping) // We split the entries into multiple priority groups if that selection is made List <PlanScratchpad> groupedPlan = new List <PlanScratchpad>(); var scratchpad = new CharacterScratchpad(m_character); if (m_groupByPriority) { foreach (var group in m_entries.GroupBy(x => x.Priority).OrderBy(x => x.Key)) { groupedPlan.Add(new PlanScratchpad(scratchpad, group)); } } else { groupedPlan.Add(new PlanScratchpad(scratchpad, m_entries)); } // Apply second pass (sorts) // We sort every group, and merge them once they're sorted List <PlanEntry> list = new List <PlanEntry>(); foreach (var group in groupedPlan) { group.UpdateStatistics(scratchpad, false, false); group.SimpleSort(m_sort, m_reverseOrder); list.AddRange(group); } // 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); }
/// <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> /// Will set the provided character scratchpad's base attributes as the target values to remap. /// </summary> /// <param name="newScratchpad">The scratchpad with the target base values to assign to this point</param> /// <param name="oldScratchpad">The scratchpad before we remapped</param> internal void SetBaseAttributes(CharacterScratchpad newScratchpad, CharacterScratchpad oldScratchpad) { // Update the status this.m_status = PointStatus.UpToDate; // Initialize the string StringBuilder builder = new StringBuilder(); // Scroll through attributes for (int i = 0; i < 5; i++) { // Compute the new base attribute EveAttribute attrib = (EveAttribute)i; m_attributes[i] = newScratchpad[attrib].Base; // Update description builder.AppendLine().Append(GetStringForAttribute(attrib, oldScratchpad, newScratchpad)); } // Return the final string this.m_description = builder.ToString(); }
/// <summary> /// Generate a trainings array from the skills already know by a character /// </summary> /// <param name="character"></param> /// <returns></returns> public static RemappingResult OptimizeFromCharacter(Character character) { // Create a character without any skill var scratchpad = new CharacterScratchpad(character); scratchpad.ClearSkills(); // Create a sorted plan for the learning skills var plan = new Plan(scratchpad); foreach (var learning in character.SkillGroups["Learning"]) { plan.PlanTo(learning, learning.Level); } plan.SortLearningSkills(false); // Add the non-training skills after that foreach (var skill in character.Skills) { if (skill.Group.Name != "Learning") { plan.PlanTo(skill, skill.Level); } } // Add those learning skills to a list var remapping = new RemappingResult(scratchpad); foreach (var entry in plan) { remapping.Skills.Add(entry); } // Optimize remapping.Optimize(TimeSpan.FromDays(365.0)); return(remapping); }
/// <summary> /// Computes an optimized scratchpad, then call <see cref="Update"/>. /// </summary> /// <param name="maxDuration">The max duration to take into account for optimization.</param> /// <returns></returns> public void Optimize(TimeSpan maxDuration) { m_bestScratchpad = AttributesOptimizer.Optimize(m_skills, m_baseScratchpad, maxDuration); Update(); }
/// <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); }
/// <summary> /// Constructor for a manually edited result from a base result. /// </summary> /// <param name="point">Associated remapping point, may be null.</param> /// <param name="baseScratchpad"></param> /// <param name="bestScratchpad"></param> public RemappingResult(RemappingResult result, CharacterScratchpad bestScratchpad) : this(result.Point, result.BaseScratchpad) { m_skills.AddRange(result.m_skills); m_bestScratchpad = bestScratchpad; }
/// <summary> /// Constructor for a result bound to a remapping point /// </summary> /// <param name="point">Associated remapping point, may be null.</param> /// <param name="baseScratchpad"></param> public RemappingResult(RemappingPoint point, CharacterScratchpad baseScratchpad) : this(baseScratchpad) { m_point = point; }
/// <summary> /// Sens a mail alert for a skill completion /// </summary> /// <param name="skillLevel"></param> /// <param name="skillName"></param> /// <param name="character"></param> /// <returns></returns> public static bool SendSkillCompletionMail(int skillLevel, string skillName, Character character) { string charName = character.Name; string skillLevelString = Skill.GetRomanForInt(skillLevel); // Message's first line StringBuilder messageText = new StringBuilder(); messageText.Append(charName + " has finished training " + skillName + " "); // Short format (also for SMS) if (Settings.Notifications.UseEmailShortFormat) { return(SendMail(Settings.Notifications, "[STC] " + charName + " :: " + skillName + " " + skillLevelString, messageText.ToString())); } // Long format messageText.Append("\r\n\r\nNext skills listed in plans:\r\n\r\n"); foreach (var plan in character.Plans) { if (plan.Count > 0) { // Print plan name CharacterScratchpad scratchpad = new CharacterScratchpad(character); messageText.Append(plan.Name + ":\r\n"); // Scroll through entries int i = 0; int minDays = 1; foreach (PlanEntry entry in plan) { TimeSpan trainTime = scratchpad.GetTrainingTime(entry.Skill, entry.Level, TrainingOrigin.FromPreviousLevelOrCurrent); // Only print the first three skills, and the very long skills (first limit is one day, then we add skills duration if (++i <= 3 || trainTime.Days > minDays) { if (i > 3) { // Print long message once if (minDays == 1) { messageText.Append("\r\n" + "Longer skills from " + plan.Name + ":\r\n"); } minDays = trainTime.Days + minDays; } messageText.Append("\t" + entry.ToString()); // Notes if (entry.Notes != null && entry.Notes.Length > 0) { messageText.Append(" (" + entry.Notes + ")"); } // Training time string timeText = String.Format("{0:00}:{1:00}:{2:00}", trainTime.Hours, trainTime.Minutes, trainTime.Seconds); if (trainTime.Days > 0) { messageText.Append(" - " + trainTime.Days + "d, " + timeText); } else { messageText.Append(" - " + timeText); } messageText.Append("\r\n"); } } messageText.Append("\r\n"); } } return(SendMail(Settings.Notifications, charName + " has finished training " + skillName + " " + skillLevelString, messageText.ToString())); }
/// <summary> /// Constructor without any remapping point associated /// </summary> /// <param name="baseScratchpad"></param> public RemappingResult(CharacterScratchpad baseScratchpad) { m_baseScratchpad = baseScratchpad; m_startTime = m_baseScratchpad.TrainingTime; }
/// <summary> /// Sends a mail alert for a skill completion /// </summary> /// <param name="queueList">Current Skill Queue</param> /// <param name="skill">Skill that has just completed</param> /// <param name="character">Character affected</param> /// <returns></returns> public static bool SendSkillCompletionMail(IList <QueuedSkill> queueList, QueuedSkill skill, Character character) { CCPCharacter ccpCharacter = character as CCPCharacter; // Current character isn't a CCP character, so can't have a Queue. if (ccpCharacter == null) { return(false); } string charName = character.Name; string skillName = skill.SkillName; string skillLevelString = Skill.GetRomanForInt(skill.Level); var skillQueueEndTime = ccpCharacter.SkillQueue.EndTime; bool freeTime = skillQueueEndTime < DateTime.UtcNow.AddHours(24); TimeSpan timeLeft = DateTime.UtcNow.AddHours(24).Subtract(skillQueueEndTime); string timeLeftText = timeLeft.ToDescriptiveText(DescriptiveTextOptions.IncludeCommas, false); // Message's first line StringBuilder body = new StringBuilder(); body.AppendFormat(CultureConstants.DefaultCulture, "{0} has finished training {1} {2}.{3}{3}", charName, skillName, skillLevelString, Environment.NewLine); // Next skills in queue if (queueList[0] != null) { body.AppendFormat(CultureConstants.DefaultCulture, "Next skill{0} in queue:{1}", (queueList.Count > 1 ? "s" : String.Empty), Environment.NewLine); foreach (var qskill in queueList) { body.AppendFormat(CultureConstants.DefaultCulture, "- {0}{1}", qskill, Environment.NewLine); } body.AppendLine(); } else { body.AppendFormat(CultureConstants.DefaultCulture, "Character is not training.{0}{0}", Environment.NewLine); } // Free room in skill queue if (freeTime) { body.AppendFormat(CultureConstants.DefaultCulture, "There is also {0} free room in skill queue.{1}", timeLeftText, Environment.NewLine); } // Short format (also for SMS) if (Settings.Notifications.UseEmailShortFormat) { return(SendMail(Settings.Notifications, String.Format(CultureConstants.DefaultCulture, "[STC] {0} :: {1} {2}", charName, skillName, skillLevelString), body.ToString())); } // Long format if (character.Plans.Count > 0) { body.AppendFormat(CultureConstants.DefaultCulture, "Next skills listed in plans:{0}{0}", Environment.NewLine); } foreach (var plan in character.Plans) { if (plan.Count > 0) { // Print plan name CharacterScratchpad scratchpad = new CharacterScratchpad(character); body.AppendFormat(CultureConstants.DefaultCulture, "{0}:{1}", plan.Name, Environment.NewLine); // Scroll through entries int i = 0; int minDays = 1; foreach (PlanEntry entry in plan) { TimeSpan trainTime = scratchpad.GetTrainingTime(entry.Skill, entry.Level, TrainingOrigin.FromPreviousLevelOrCurrent); // Only print the first three skills, and the very long skills // (first limit is one day, then we add skills duration) if (++i <= 3 || trainTime.Days > minDays) { if (i > 3) { // Print long message once if (minDays == 1) { body.AppendFormat(CultureConstants.DefaultCulture, "{1}Longer skills from {0}:{1}", plan.Name, Environment.NewLine); } minDays = trainTime.Days + minDays; } body.AppendFormat(CultureConstants.DefaultCulture, "\t{0}", entry); // Notes if (entry.Notes != null && entry.Notes.Length > 0) { body.AppendFormat(CultureConstants.DefaultCulture, " ({0})", entry.Notes); } // Training time if (trainTime.Days > 0) { body.AppendFormat(CultureConstants.DefaultCulture, " - {0}d, {1}", trainTime.Days, trainTime); } else { body.AppendFormat(CultureConstants.DefaultCulture, " - {0}", trainTime); } body.AppendLine(); } } body.AppendLine(); } } string subject = String.Format(CultureConstants.DefaultCulture, "{0} has finished training {1} {2}", charName, skillName, skillLevelString); return(SendMail(Settings.Notifications, subject, body.ToString())); }
/// <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); }