diff --git a/src/EVEMon.Common/Models/BaseCharacter.cs b/src/EVEMon.Common/Models/BaseCharacter.cs index d9661dfad..99f4412d3 100644 --- a/src/EVEMon.Common/Models/BaseCharacter.cs +++ b/src/EVEMon.Common/Models/BaseCharacter.cs @@ -368,7 +368,7 @@ public TimeSpan GetTrainingTimeWithoutBoosters(StaticSkill skill, long level, Tr /// /// /// - private static TimeSpan GetTrainingTime(long sp, float spPerHour) + internal static TimeSpan GetTrainingTime(long sp, float spPerHour) => Math.Abs(spPerHour) < float.Epsilon ? TimeSpan.FromDays(999.0) : TimeSpan.FromHours(sp / spPerHour); /// diff --git a/src/EVEMon.Common/Models/QueuedSkill.cs b/src/EVEMon.Common/Models/QueuedSkill.cs index 2431026ed..bba7dde05 100644 --- a/src/EVEMon.Common/Models/QueuedSkill.cs +++ b/src/EVEMon.Common/Models/QueuedSkill.cs @@ -182,6 +182,31 @@ public double SkillPointsPerHour } } + /// + /// Gets the training speed without boosters. + /// + /// + public double SkillPointsPerHourWithoutBoosters + { + get + { + double rate; + if (Skill == Skill.UnknownSkill) + { + // Based on estimated end time - start time + double time = EndTime.Subtract(StartTime).TotalHours; + if (time <= 0.0) + // Do not divide by zero + rate = 0.0; + else + rate = Math.Ceiling((EndSP - StartSP) / time); + } + else + rate = Skill.SkillPointsPerHourWithoutBoosters; + return rate; + } + } + /// /// Computes the remaining time. /// @@ -212,11 +237,69 @@ public bool IsTraining } } + public TimeSpan BoosterDuration + { + get + { + var remainingTime = RemainingTime; + var queueTime = EndTime - StartTime; + + var expectedTime = Owner.GetTimeSpanForPointsWithoutBoosters(Skill.StaticData, Level); + + var actualSPRate = Owner.GetBaseSPPerHour(Skill.StaticData); + var expectedSPRate = Owner.GetBaseSPPerHourWithoutBoosters(Skill.StaticData); + + if (expectedTime > remainingTime || !IsTraining && (expectedTime > queueTime)) + { + // Booster detected! + var remainingSP = EndSP - CurrentSP; + var expectedSPInActualTime = IsTraining ? + Math.Round(remainingTime.TotalHours * expectedSPRate) : Math.Round(queueTime.TotalHours * expectedSPRate); + + var spRateDiff = actualSPRate - expectedSPRate; + + if (spRateDiff <= 0) + { + return TimeSpan.Zero; + } + + var boosterHours = (remainingSP - expectedSPInActualTime) / spRateDiff; + + return TimeSpan.FromHours(boosterHours); + } + return TimeSpan.Zero; + } + } + /// /// Gets true if the training has been completed, false otherwise. /// public bool IsCompleted => EndTime <= DateTime.UtcNow; + /// + /// Calculate the time it will take to train a certain amount of skill points. + /// + /// The amount of skill points. + /// Time it will take. + public TimeSpan GetTimeSpanForPoints(long points) + { + if (BoosterDuration > TimeSpan.Zero) + { + var c = Owner as CCPCharacter; + if (c == null) + { + return TimeSpan.Zero; + } + + var actualSPRate = c.GetBaseSPPerHour(Skill.StaticData); + var expectedSPRate = c.GetBaseSPPerHourWithoutBoosters(Skill.StaticData); + + return Skill.GetTimeSpanForPoints(points, expectedSPRate, actualSPRate, BoosterDuration); + } + + return Skill.GetTimeSpanForPoints(points); + } + public override bool Equals(object obj) { var other = obj as QueuedSkill; diff --git a/src/EVEMon.Common/Models/Skill.cs b/src/EVEMon.Common/Models/Skill.cs index a64b1ed2b..d26777bd2 100644 --- a/src/EVEMon.Common/Models/Skill.cs +++ b/src/EVEMon.Common/Models/Skill.cs @@ -501,9 +501,26 @@ public Skill ToCharacter(Character character) /// /// The amount of skill points. /// Time it will take. - public TimeSpan GetTimeSpanForPoints(long points) + public TimeSpan GetTimeSpanForPoints(long points) => Character?.GetTimeSpanForPoints(this, points) ?? TimeSpan.Zero; + public TimeSpan GetTimeSpanForPoints(long points, float normalRate, float boostedRate, TimeSpan boostedDuration) + { + var boostedPoints = (long)Math.Round(boostedRate * boostedDuration.TotalHours); + var normalPoints = points - boostedPoints; + + var boostedTime = BaseCharacter.GetTrainingTime(boostedPoints, boostedRate); + var normalTime = BaseCharacter.GetTrainingTime(normalPoints, normalRate); + + if (normalPoints <= 0) + { + // Skill will complete training while booster is active! + return boostedTime; + } + + return normalTime + boostedTime; + } + /// /// Calculates the cumulative points required to reach the given level of this skill, starting from the current SP. /// diff --git a/src/EVEMon.Common/Models/SkillQueue.cs b/src/EVEMon.Common/Models/SkillQueue.cs index d027dce01..3f0846252 100644 --- a/src/EVEMon.Common/Models/SkillQueue.cs +++ b/src/EVEMon.Common/Models/SkillQueue.cs @@ -64,35 +64,7 @@ internal void Dispose() /// /// Gets the expected booster duration /// - public TimeSpan BoosterDuration => !Items.Any() ? TimeSpan.Zero : TimeSpan.FromHours(Items.Select(i => { - var remainingTime = i.RemainingTime; - var queueTime = i.EndTime - i.StartTime; - - var expectedTime = m_character.GetTimeSpanForPointsWithoutBoosters(i.Skill.StaticData, i.Level); - - var actualSPRate = m_character.GetBaseSPPerHour(i.Skill.StaticData); - var expectedSPRate = m_character.GetBaseSPPerHourWithoutBoosters(i.Skill.StaticData); - - if (expectedTime > remainingTime || !i.IsTraining && (expectedTime > queueTime)) - { - // Booster detected! - var remainingSP = i.EndSP - i.CurrentSP; - var expectedSPInActualTime = i.IsTraining ? - Math.Round(remainingTime.TotalHours * expectedSPRate) : Math.Round(queueTime.TotalHours * expectedSPRate); - - var spRateDiff = actualSPRate - expectedSPRate; - - if (spRateDiff <= 0) - { - return TimeSpan.Zero.TotalHours; - } - - var boosterHours = (remainingSP - expectedSPInActualTime) / spRateDiff; - - return boosterHours; - } - return TimeSpan.Zero.TotalHours; - }).Sum()); + public TimeSpan BoosterDuration => !Items.Any() ? TimeSpan.Zero : TimeSpan.FromHours(Items.Select(i => i.BoosterDuration.TotalHours).Sum()); /// /// Gets the skill currently in training. diff --git a/src/EVEMon/CharacterMonitoring/CharacterSkillsQueueList.cs b/src/EVEMon/CharacterMonitoring/CharacterSkillsQueueList.cs index c5aab20a1..5a064d856 100644 --- a/src/EVEMon/CharacterMonitoring/CharacterSkillsQueueList.cs +++ b/src/EVEMon/CharacterMonitoring/CharacterSkillsQueueList.cs @@ -234,15 +234,15 @@ private void DrawItem(QueuedSkill skill, DrawItemEventArgs e) skill.Skill.StaticData.GetPointsRequiredForLevel(Math.Min(skill.Level, 5)); long pointsLeft = skillPointsToNextLevel - skillPoints; TimeSpan timeSpanFromPoints = !hasSkill ? skill.EndTime.Subtract(DateTime.UtcNow) : - skill.Skill.GetTimeSpanForPoints(pointsLeft); + skill.GetTimeSpanForPoints(pointsLeft); string remainingTimeText = timeSpanFromPoints.ToDescriptiveText( DescriptiveTextOptions.SpaceBetween); double fractionCompleted = e.Index == 0 ? skill.FractionCompleted : 0.0; - string indexText = $"{e.Index + 1}. "; + string indexText = $"{e.Index + 1}. {(skill.BoosterDuration > TimeSpan.Zero ? "\u26a1" : "")}"; string rankText = $" (Rank {(skill.Skill == null ? 0 : skill.Rank)})"; - string spPerHourText = $" SP/Hour: {skill.SkillPointsPerHour}"; + string spPerHourText = $" SP/Hour: {(skill.BoosterDuration > TimeSpan.Zero ? skill.SkillPointsPerHour : skill.SkillPointsPerHourWithoutBoosters)}"; string spText = $"SP: {skillPoints:N0}/{skillPointsToNextLevel:N0}"; string trainingTimeText = $" Training Time: {remainingTimeText}"; string levelText = $"Level {skill.Level}"; @@ -646,7 +646,7 @@ private static string GetTooltip(QueuedSkill skill) long pointsLeft = nextLevelSP - sp; TimeSpan timeSpanFromPoints = skill.Skill == Skill.UnknownSkill ? skill.EndTime.Subtract(DateTime.UtcNow) - : skill.Skill.GetTimeSpanForPoints(pointsLeft); + : skill.GetTimeSpanForPoints(pointsLeft); string remainingTimeText = timeSpanFromPoints.ToDescriptiveText( DescriptiveTextOptions.IncludeCommas | DescriptiveTextOptions.UppercaseText); @@ -769,7 +769,7 @@ private static void AddSkillBoilerPlate(StringBuilder toolTip, QueuedSkill skill toolTip.AppendLine().AppendLine(skill.Skill.Description.WordWrap(100)); toolTip.Append("Primary: ").Append(skill.Skill.PrimaryAttribute).Append(", "); toolTip.Append("Secondary: ").Append(skill.Skill.SecondaryAttribute).Append(" ("); - toolTip.AppendFormat("{0:F0}", skill.SkillPointsPerHour).Append(" SP/Hour)"); + toolTip.AppendFormat("{0:F0}", skill.BoosterDuration > TimeSpan.Zero ? skill.SkillPointsPerHour : skill.SkillPointsPerHourWithoutBoosters).Append(" SP/Hour)"); } ///