Пример #1
0
        /// <summary>
        /// Updates the training skill info.
        /// </summary>
        private void UpdateTrainingSkillInfo()
        {
            QueuedSkill training       = m_character.CurrentlyTrainingSkill;
            DateTime    completionTime = training.EndTime.ToLocalTime();

            lblTrainingSkill.Text = training.ToString();
            lblSPPerHour.Text     = training.Skill == null
                ? "???"
                : $"{training.SkillPointsPerHour} SP/Hour";
            lblTrainingEst.Text = $"{completionTime:ddd} {completionTime:G}";

            // Dipslay a warning if anything scheduled is blocking us

            string       conflictMessage;
            bool         isAutoBlocking;
            bool         isBlocking   = Scheduler.SkillIsBlockedAt(training.EndTime.ToLocalTime(), out conflictMessage, out isAutoBlocking);
            CCPCharacter ccpCharacter = m_character as CCPCharacter;

            // Do not show the "DOWNTIME" warning if character's skill queue has more than one skills
            if (ccpCharacter != null && ccpCharacter.SkillQueue.Count > 1 &&
                isAutoBlocking &&
                string.Equals(conflictMessage, EveMonConstants.DowntimeText, StringComparison.OrdinalIgnoreCase))
            {
                lblScheduleWarning.Visible = false;
                lblScheduleWarning.Text    = string.Empty;
                return;
            }

            lblScheduleWarning.Visible = isBlocking;
            lblScheduleWarning.Text    = conflictMessage;
        }
Пример #2
0
        /// <summary>
        /// Updates the skill queue info if queue is paused.
        /// </summary>
        /// <returns></returns>
        private bool SkillQueueIsPaused()
        {
            CCPCharacter ccpCharacter = m_character as CCPCharacter;

            if (ccpCharacter == null || !ccpCharacter.SkillQueue.IsPaused)
            {
                return(false);
            }

            QueuedSkill training = ccpCharacter.SkillQueue.CurrentlyTraining;

            lblTrainingSkill.Text = training.ToString();
            lblSPPerHour.Text     = training.Skill == null
                ? "???"
                : $"{training.Skill.SkillPointsPerHour} SP/Hour";

            lblTrainingRemain.Text      = "Paused";
            lblTrainingEst.Text         = string.Empty;
            lblScheduleWarning.Visible  = false;
            skillQueueTimePanel.Visible = false;
            skillQueuePanel.Visible     = true;
            pnlTraining.Visible         = true;
            lblPaused.Visible           = true;

            return(true);
        }
Пример #3
0
        /// <summary>
        /// Updates the skill queue info.
        /// </summary>
        private void UpdateSkillQueueInfo()
        {
            CCPCharacter ccpCharacter = m_character as CCPCharacter;

            if (ccpCharacter == null)
            {
                return;
            }

            DateTime queueCompletionTime = ccpCharacter.SkillQueue.EndTime.ToLocalTime();

            lblQueueCompletionTime.Text = $"{queueCompletionTime:ddd} {queueCompletionTime:G}";

            // Skill queue time panel
            skillQueueTimePanel.Visible = ccpCharacter.SkillQueue.Count > 1 || Settings.UI.MainWindow.AlwaysShowSkillQueueTime ||
                                          (ccpCharacter.SkillQueue.Count == 1 && Settings.UI.MainWindow.AlwaysShowSkillQueueTime);

            // Update the remaining training time label
            QueuedSkill training = m_character.CurrentlyTrainingSkill;

            lblTrainingRemain.Text = training.EndTime.ToRemainingTimeDescription(DateTimeKind.Utc);

            // Update the remaining queue time label
            DateTime queueEndTime = ccpCharacter.SkillQueue.EndTime;

            lblQueueRemaining.Text = queueEndTime.ToRemainingTimeDescription(DateTimeKind.Utc);
        }
Пример #4
0
        /// <summary>
        /// Gets the rectangle a skill rendes in within a specified rectange.
        /// </summary>
        /// <param name="skill">Skill that exists within the queue</param>
        /// <param name="width">Width of the canvas</param>
        /// <param name="height">Height of the canvas</param>
        /// <returns>
        /// Rectangle representing the area within the visual
        /// queue the skill occupies.
        /// </returns>
        internal static Rectangle GetSkillRect(QueuedSkill skill, int width, int height)
        {
            TimeSpan relativeStart;
            TimeSpan relativeFinish;

            // Character is training ? we update the timespan
            if (skill.Owner.IsTraining)
            {
                relativeStart  = skill.StartTime.Subtract(DateTime.UtcNow);
                relativeFinish = skill.EndTime.Subtract(DateTime.UtcNow);
            }
            // Timespan is stable
            else
            {
                relativeStart  = skill.StartTime.Subtract(m_paintTime);
                relativeFinish = skill.EndTime.Subtract(m_paintTime);
            }

            int TotalSeconds = (int)TimeSpan.FromHours(24).TotalSeconds;

            double Start  = Math.Floor((relativeStart.TotalSeconds / TotalSeconds) * width);
            double Finish = Math.Floor((relativeFinish.TotalSeconds / TotalSeconds) * width);

            // If the start time is before now set it to zero
            if (Start < 0)
            {
                Start = 0;
            }

            return(new Rectangle((int)Start, 0, (int)(Finish - Start), height));
        }
Пример #5
0
 /// <summary>
 /// Adds the skill boiler plate.
 /// </summary>
 /// <param name="toolTip">The tool tip.</param>
 /// <param name="skill">The skill.</param>
 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)");
 }
Пример #6
0
        /// <summary>
        /// Draws the queue color bar.
        /// </summary>
        /// <param name="skill">The skill.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.DrawItemEventArgs"/> instance containing the event data.</param>
        private void DrawQueueColorBar(QueuedSkill skill, DrawItemEventArgs e)
        {
            Graphics g = e.Graphics;

            // Draw the background
            Rectangle qBarRect = new Rectangle(e.Bounds.Left, GetItemHeight - LowerBoxHeight, e.Bounds.Width, LowerBoxHeight);

            g.FillRectangle(Brushes.DimGray, qBarRect);

            double             oneDaySkillQueueWidth = Character.SkillQueue.GetOneDaySkillQueueWidth(qBarRect.Width);
            IList <RectangleF> skillRects            = Character.SkillQueue.GetSkillRects(skill, qBarRect.Width, LowerBoxHeight - 1).ToList();
            Brush      lessThanDayBrush = Settings.UI.SafeForWork ? Brushes.LightGray : Brushes.Khaki;
            Brush      moreThanDayBrush = Settings.UI.SafeForWork ? Brushes.DarkGray : Brushes.CornflowerBlue;
            RectangleF skillRectFirst   = skillRects.First();

            // Skill starts before the 24h marker
            if (skillRectFirst.X < oneDaySkillQueueWidth)
            {
                // Iterate only through rectangles with width
                foreach (RectangleF skillRect in skillRects.Skip(1).Where(rect => rect.Width > 0))
                {
                    Brush brush = oneDaySkillQueueWidth - skillRect.X <= 0 ? moreThanDayBrush : lessThanDayBrush;

                    g.FillRectangle(brush,
                                    new RectangleF(skillRect.X, GetItemHeight - LowerBoxHeight, skillRect.Width, skillRect.Height));
                }
                return;
            }

            g.FillRectangle(moreThanDayBrush,
                            new RectangleF(skillRectFirst.X, GetItemHeight - LowerBoxHeight, skillRectFirst.Width, skillRectFirst.Height));
        }
Пример #7
0
        /// <summary>
        /// On timer tick, we invalidate the training skill display
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void EveClient_TimerTick(object sender, EventArgs e)
        {
            if (Character.IsTraining && Visible)
            {
                // Retrieves the trained skill for update but quit if the skill is null (was not in our datafiles)
                QueuedSkill trainingSkill = Character.CurrentlyTrainingSkill;
                if (trainingSkill == null)
                {
                    return;
                }

                // Invalidate the currently training skill level row
                int index = lbSkillsQueue.Items.IndexOf(trainingSkill);
                if (index == 0)
                {
                    lbSkillsQueue.Invalidate(lbSkillsQueue.GetItemRectangle(index));
                }

                // When there are more than one skill level rows in queue, we invalidate them on a timer
                if (lbSkillsQueue.Items.Count > 1 && DateTime.Now > m_nextRepainting)
                {
                    lbSkillsQueue.Invalidate();
                }

                UpdateContent();
            }
        }
Пример #8
0
        /// <summary>
        /// Process the queue into Google calendar.
        /// </summary>
        /// <param name="queuedSkill">The queued skill.</param>
        /// <param name="queuePosition">The queue position.</param>
        /// <param name="lastSkillInQueue">if set to <c>true</c> skill is the last in queue.</param>
        private static async Task DoGoogleAppointmentAsync(QueuedSkill queuedSkill, int queuePosition, bool lastSkillInQueue)
        {
            try
            {
                // Set the subject to the character name and the skill and level in queue for uniqueness sakes
                GoogleCalendarEvent googleAppointmentFilter = new GoogleCalendarEvent
                {
                    StartDate = DateTime.Now.AddDays(-40),
                    EndDate   = DateTime.Now.AddDays(100),
                    Subject   = string.Format(
                        CultureConstants.DefaultCulture,
                        "{0} - {1} {2}",
                        queuedSkill.Owner.Name,
                        queuedSkill.SkillName,
                        Skill.GetRomanFromInt(queuedSkill.Level))
                };

                // Pull the list of appointments, hopefully we should either get 1 or none back
                await googleAppointmentFilter.ReadEventsAsync();

                // If there is are appointments, see if any match the subject
                bool foundAppointment = false;
                if (googleAppointmentFilter.ItemCount > 0)
                {
                    foundAppointment = googleAppointmentFilter.GetEvent();
                }

                // Update the appointment we may have pulled or the new one
                // Set the appointment length to 5 minutes, starting at the estimated completion date and time
                // Reminder interval was already validated
                // Use the values from the screen as these may differ what the user has set for defaults
                googleAppointmentFilter.StartDate         = queuedSkill.EndTime.ToLocalTime();
                googleAppointmentFilter.EndDate           = queuedSkill.EndTime.ToLocalTime().AddMinutes(5);
                googleAppointmentFilter.ItemReminder      = Settings.Calendar.UseReminding;
                googleAppointmentFilter.AlternateReminder = Settings.Calendar.UseAlternateReminding;
                googleAppointmentFilter.EarlyReminder     = Settings.Calendar.EarlyReminding;
                googleAppointmentFilter.LateReminder      = Settings.Calendar.LateReminding;
                googleAppointmentFilter.Minutes           = Settings.Calendar.RemindingInterval;
                googleAppointmentFilter.ReminderMethod    = Settings.Calendar.GoogleEventReminder;

                await googleAppointmentFilter.AddOrUpdateEventAsync(foundAppointment, queuePosition, lastSkillInQueue);
            }
            catch (TokenResponseException ex)
            {
                MessageBox.Show(ex.Error.ErrorDescription, @"Google Calendar");
            }
            catch (GoogleApiException ex)
            {
                MessageBox.Show(ex.Error.Message, @"Google Calendar");
            }
            catch (APIException ex)
            {
                MessageBox.Show(ex.Message, ex.ErrorCode ?? @"Google Calendar");
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, @"Google Calendar");
            }
        }
Пример #9
0
 /// <summary>
 /// Handles the DrawItem event of the lbSkillsQueue control.
 /// </summary>
 /// <param name="sender">The source of the event.</param>
 /// <param name="e">The <see cref="System.Windows.Forms.DrawItemEventArgs"/> instance containing the event data.</param>
 private void lbSkillsQueue_DrawItem(object sender, DrawItemEventArgs e)
 {
     if (e.Index < 0)
     {
         return;
     }
     item = lbSkillsQueue.Items[e.Index] as QueuedSkill;
     DrawItem(item, e);
 }
Пример #10
0
 /// <summary>
 /// Adds the skill boiler plate.
 /// </summary>
 /// <param name="toolTip">The tool tip.</param>
 /// <param name="skill">The skill.</param>
 private static void AddSkillBoilerPlate(StringBuilder toolTip, QueuedSkill skill)
 {
     toolTip
     .AppendLine()
     .AppendLine(skill.Skill.Description.WordWrap(100))
     .Append($"Primary: {skill.Skill.PrimaryAttribute}, ")
     .Append($"Secondary: {skill.Skill.SecondaryAttribute} ")
     .Append($"({skill.SkillPointsPerHour} SP/Hour)");
 }
Пример #11
0
        /// <summary>
        /// On a mouse down event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lbSkills_MouseDown(object sender, MouseEventArgs e)
        {
            // Retrieve the item at the given point and quit if none
            int index = lbSkillsQueue.IndexFromPoint(e.X, e.Y);

            if (index < 0 || index >= lbSkillsQueue.Items.Count)
            {
                return;
            }

            // Beware, this last index may actually means a click in the whitespace at the bottom
            // Let's deal with this special case
            if (index == lbSkillsQueue.Items.Count - 1)
            {
                Rectangle itemRect = lbSkillsQueue.GetItemRectangle(index);
                if (!itemRect.Contains(e.Location))
                {
                    return;
                }
            }

            // Right click for skills below lv5 : we display a context menu to plan higher levels.
            item    = lbSkillsQueue.Items[index] as QueuedSkill;
            m_skill = item.Skill;
            if (e.Button == MouseButtons.Right && m_skill.Level < 5)
            {
                // Reset the menu.
                ToolStripMenuItem tm = new ToolStripMenuItem(String.Format(CultureInfo.CurrentCulture, "Add {0}", m_skill.Name));

                // Build the level options.
                int nextLevel = Math.Min(5, m_skill.Level + 1);
                for (int level = nextLevel; level < 6; level++)
                {
                    ToolStripMenuItem menuLevel = new ToolStripMenuItem(String.Format(CultureInfo.CurrentCulture, "Level {0} to", Skill.GetRomanForInt(level)));
                    tm.DropDownItems.Add(menuLevel);
                    foreach (var plan in m_character.Plans)
                    {
                        ToolStripMenuItem menuPlanItem = new ToolStripMenuItem(plan.Name);
                        menuPlanItem.Click += new EventHandler(menuPlanItem_Click);
                        menuPlanItem.Tag    = new Pair <Plan, SkillLevel>(plan, new SkillLevel(m_skill, level));
                        menuLevel.DropDownItems.Add(menuPlanItem);
                    }
                }

                // Add to the context menu and display
                contextMenuStripPlanPopup.Items.Clear();
                contextMenuStripPlanPopup.Items.Add(tm);
                contextMenuStripPlanPopup.Show((Control)sender, new Point(e.X, e.Y));
                return;
            }

            // Non-right click or already lv5, display the tooltip
            DisplayTooltip(item);
        }
Пример #12
0
        /// <summary>
        /// Displays the tooltip for the given skill.
        /// </summary>
        /// <param name="item"></param>
        private void DisplayTooltip(QueuedSkill item)
        {
            if (ttToolTip.Active && m_lastTooltipItem == item)
            {
                return;
            }
            m_lastTooltipItem = item;

            ttToolTip.Active = false;
            ttToolTip.SetToolTip(lbSkillsQueue, GetTooltip(item));
            ttToolTip.Active = true;
        }
Пример #13
0
        /// <summary>
        /// Draws the boxes.
        /// </summary>
        /// <param name="fractionCompleted">The fraction completed.</param>
        /// <param name="skill">The skill.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.DrawItemEventArgs"/> instance containing the event data.</param>
        private void DrawBoxes(double fractionCompleted, QueuedSkill skill, DrawItemEventArgs e)
        {
            Graphics g = e.Graphics;

            g.DrawRectangle(Pens.Black,
                            new Rectangle(e.Bounds.Right - BoxWidth - PadRight, e.Bounds.Top + PadTop, BoxWidth,
                                          BoxHeight));

            const int LevelBoxWidth = (BoxWidth - 4 - 3) / 5;

            for (int level = 1; level <= 5; level++)
            {
                Rectangle brect =
                    new Rectangle(e.Bounds.Right - BoxWidth - PadRight + 2 + LevelBoxWidth * (level - 1) + (level - 1),
                                  e.Bounds.Top + PadTop + 2, LevelBoxWidth, BoxHeight - 3);

                // Box color
                g.FillRectangle(skill.Skill != null && level < skill.Level ? Brushes.Black : Brushes.DarkGray, brect);

                if (skill.Skill == null)
                {
                    continue;
                }

                foreach (QueuedSkill qskill in Character.SkillQueue)
                {
                    if ((!qskill.IsTraining && skill == qskill && level == qskill.Level) ||
                        (skill == qskill && level <= qskill.Level && level > skill.Skill.Level &&
                         Math.Abs(fractionCompleted) < double.Epsilon))
                    {
                        g.FillRectangle(Brushes.RoyalBlue, brect);
                    }

                    // Blinking indicator of skill level in training
                    if (!qskill.IsTraining || skill != qskill || level != skill.Level ||
                        Math.Abs(fractionCompleted) < double.Epsilon)
                    {
                        continue;
                    }

                    if (m_blinkAction == BlinkAction.Blink)
                    {
                        g.FillRectangle(Brushes.RoyalBlue, brect);
                    }

                    m_blinkAction = m_blinkAction == BlinkAction.Reset
                        ? BlinkAction.Blink
                        : BlinkAction.Stop;
                }
            }
        }
Пример #14
0
        /// <summary>
        /// Process the queue into MS Outlook.
        /// </summary>
        /// <param name="queuedSkill">The queued skill.</param>
        /// <param name="queuePosition">The queue position.</param>
        /// <param name="lastSkillInQueue">if set to <c>true</c> skill is the last in queue.</param>
        private static async Task DoOutlookAppointmentAsync(QueuedSkill queuedSkill, int queuePosition, bool lastSkillInQueue)
        {
            // Get the calendar
            if (!OutlookCalendarEvent.OutlookCalendarExist(Settings.Calendar.UseOutlookDefaultCalendar))
            {
                MessageBox.Show(@"Outlook calendar does not exist. Please check your settings.", @"Outlook Error",
                                MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            // Set the subject to the character name and the skill and level in queue for uniqueness sake
            OutlookCalendarEvent outlookAppointmentFilter = new OutlookCalendarEvent
            {
                StartDate = DateTime.Now.AddDays(-40),
                EndDate   = DateTime.Now.AddDays(100),
                Subject   = string.Format(
                    CultureConstants.DefaultCulture,
                    "{0} - {1} {2}",
                    queuedSkill.Owner.Name,
                    queuedSkill.SkillName,
                    Skill.GetRomanFromInt(queuedSkill.Level))
            };


            // Pull the list of appointments, hopefully we should either get 1 or none back
            await outlookAppointmentFilter.ReadEventsAsync();

            // If there is an appointment, get the first one
            bool foundAppointment = false;

            if (outlookAppointmentFilter.ItemCount > 0)
            {
                foundAppointment = outlookAppointmentFilter.GetEvent();
            }

            // Update the appointment we may have pulled or the new one
            // Set the appointment length to 5 minutes, starting at the estimated completion date and time
            // Reminder value was already validated
            // Use the values from the screen as these may differ what the user has set for defaults
            outlookAppointmentFilter.StartDate         = queuedSkill.EndTime.ToLocalTime();
            outlookAppointmentFilter.EndDate           = queuedSkill.EndTime.ToLocalTime().AddMinutes(5);
            outlookAppointmentFilter.ItemReminder      = Settings.Calendar.UseReminding;
            outlookAppointmentFilter.AlternateReminder = Settings.Calendar.UseAlternateReminding;
            outlookAppointmentFilter.EarlyReminder     = Settings.Calendar.EarlyReminding;
            outlookAppointmentFilter.LateReminder      = Settings.Calendar.LateReminding;
            outlookAppointmentFilter.Minutes           = Settings.Calendar.RemindingInterval;

            await outlookAppointmentFilter.AddOrUpdateEventAsync(foundAppointment, queuePosition, lastSkillInQueue);
        }
Пример #15
0
        /// <summary>
        /// Handles the DrawItem event of the lbSkillsQueue control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.DrawItemEventArgs"/> instance containing the event data.</param>
        private void lbSkillsQueue_DrawItem(object sender, DrawItemEventArgs e)
        {
            if (e.Index < 0 || e.Index >= lbSkillsQueue.Items.Count)
            {
                return;
            }

            QueuedSkill item = lbSkillsQueue.Items[e.Index] as QueuedSkill;

            if (item == null)
            {
                return;
            }

            DrawItem(item, e);
        }
Пример #16
0
        /// <summary>
        /// Displays the tooltip for the given skill.
        /// </summary>
        /// <param name="skill"></param>
        private void DisplayTooltip(QueuedSkill skill)
        {
            if (skill == null)
            {
                return;
            }

            if (ttToolTip.Active && m_lastTooltipItem != null && m_lastTooltipItem == skill)
            {
                return;
            }

            m_lastTooltipItem = skill;

            ttToolTip.Active = false;
            ttToolTip.SetToolTip(lbSkillsQueue, GetTooltip(skill));
            ttToolTip.Active = true;
        }
Пример #17
0
        /// <summary>
        /// Gets the rectangle a skill rendes in within a specified
        /// rectange.
        /// </summary>
        /// <param name="skill">Skill that exists within the queue</param>
        /// <param name="width">Width of the canvas</param>
        /// <param name="height">Height of the canvas</param>
        /// <returns>
        /// Rectangle representing the area within the visual
        /// queue the skill occupies.
        /// </returns>
        private Rectangle GetSkillRect(QueuedSkill skill, int width, int height)
        {
            TimeSpan relativeStart  = skill.StartTime - DateTime.UtcNow;
            TimeSpan relativeFinish = skill.EndTime - DateTime.UtcNow;

            int TotalSeconds = (int)TimeSpan.FromHours(24).TotalSeconds;

            double Start  = Math.Floor((relativeStart.TotalSeconds / TotalSeconds) * width);
            double Finish = Math.Floor((relativeFinish.TotalSeconds / TotalSeconds) * width);

            // if the start time is before now set it to zero
            if (Start < 0)
            {
                Start = 0;
            }

            return(new Rectangle((int)Start, 0, (int)(Finish - Start), height));
        }
Пример #18
0
        /// <summary>
        /// On mouse move, we hide the tooltip.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lbSkills_MouseMove(object sender, MouseEventArgs e)
        {
            for (int i = 0; i < lbSkillsQueue.Items.Count; i++)
            {
                // Skip until we found the mouse location
                Rectangle rect = lbSkillsQueue.GetItemRectangle(i);
                if (!rect.Contains(e.Location))
                {
                    continue;
                }

                // Updates the tooltip
                m_item = lbSkillsQueue.Items[i] as QueuedSkill;
                DisplayTooltip(m_item);
                return;
            }

            // If we went so far, we're not over anything.
            m_lastTooltipItem = null;
            ttToolTip.Active  = false;
        }
Пример #19
0
        /// <summary>
        /// Updates the tooltip's content.
        /// </summary>
        protected override void UpdateContent()
        {
            if (!Visible)
            {
                UpdatePending = true;
                return;
            }
            UpdatePending = false;

            UpdateCharactersList();

            // Replaces the fragments like "%10546464r" (the number being the character ID) by the remaining time
            string tooltip = m_tooltipFormat ?? String.Empty;

            foreach (Character character in m_characters)
            {
                if (character.IsTraining)
                {
                    QueuedSkill trainingSkill = character.CurrentlyTrainingSkill;
                    TimeSpan    remainingTime = trainingSkill.EndTime.Subtract(DateTime.UtcNow);

                    tooltip = Regex.Replace(tooltip,
                                            $"%{character.CharacterID}r",
                                            remainingTime.ToDescriptiveText(DescriptiveTextOptions.IncludeCommas),
                                            RegexOptions.Compiled);
                }

                CCPCharacter ccpCharacter = character as CCPCharacter;
                if (ccpCharacter != null && ccpCharacter.SkillQueue.IsPaused)
                {
                    tooltip = Regex.Replace(tooltip,
                                            $"%{character.CharacterID}r",
                                            "(Paused)", RegexOptions.Compiled);
                }
            }

            // Updates the tooltip and its location
            ToolTipLabel.Text = tooltip;
            TrayIcon.SetToolTipLocation(this);
        }
Пример #20
0
        /// <summary>
        /// Compares the two characters by their training completion time or, when not in training their names
        /// </summary>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentNullException">
        /// x
        /// or
        /// y
        /// </exception>
        public static int CompareByCompletionTime(Character x, Character y)
        {
            x.ThrowIfNull(nameof(x));

            y.ThrowIfNull(nameof(y));

            // Get their training skills
            QueuedSkill skillX = x.CurrentlyTrainingSkill;
            QueuedSkill skillY = y.CurrentlyTrainingSkill;

            if (skillX == null && skillY == null)
            {
                return(String.Compare(x.Name, y.Name, StringComparison.CurrentCulture));
            }
            if (skillX == null || skillY == null)
            {
                return(-1);
            }

            // Compare end time
            return(DateTime.Compare(skillX.EndTime, skillY.EndTime));
        }
Пример #21
0
        /// <summary>
        /// Displays the skill tool tip.
        /// </summary>
        /// <param name="skillRect">The skill rect.</param>
        /// <param name="skill">The skill.</param>
        private void DisplaySkillToolTip(RectangleF skillRect, QueuedSkill skill)
        {
            const string Format     = "{0} {1}\n  Start{2}\t{3}\n  Ends\t{4}";
            string       skillName  = skill.SkillName;
            string       skillLevel = Skill.GetRomanFromInt(skill.Level);
            string       skillStart = skill.Owner.IsTraining
                ? skill.StartTime.ToLocalTime().ToAbsoluteDateTimeDescription(DateTimeKind.Local)
                : "Paused";
            string skillEnd = skill.Owner.IsTraining
                ? skill.EndTime.ToLocalTime().ToAbsoluteDateTimeDescription(DateTimeKind.Local)
                : "Paused";
            string startText = skill.StartTime < DateTime.UtcNow ? "ed" : "s";
            string text      = string.Format(CultureConstants.DefaultCulture, Format, skillName, skillLevel, startText, skillStart,
                                             skillEnd);
            Size  textSize    = TextRenderer.MeasureText(text, Font);
            Size  toolTipSize = new Size(textSize.Width + 13, textSize.Height + 11);
            Point tipPoint    = new Point((int)(Math.Min(skillRect.Right, Width) + skillRect.Left) / 2 - toolTipSize.Width / 2,
                                          -toolTipSize.Height);

            tipPoint.Offset(0, -21);
            m_toolTip.Show(text, tipPoint);
        }
Пример #22
0
        /// <summary>
        /// Builds the context menu for the selected queued skill.
        /// </summary>
        /// <param name="queuedSkill">The queued skill.</param>
        private void BuildContextMenu(QueuedSkill queuedSkill)
        {
            if (m_selectedSkill == null)
            {
                return;
            }

            tsmiAddSkill.DropDownItems.Clear();

            // Reset the menu
            tsmiAddSkill.Text = $"Add {queuedSkill.Skill.Name}";

            // Build the level options
            for (long level = queuedSkill.Level; level <= 5; level++)
            {
                ToolStripMenuItem tempMenuLevel = null;
                try
                {
                    tempMenuLevel = new ToolStripMenuItem($"Level {Skill.GetRomanFromInt(level)} to");

                    Character.Plans.AddTo(tempMenuLevel.DropDownItems,
                                          (menuPlanItem, plan) =>
                    {
                        menuPlanItem.Click += menuPlanItem_Click;
                        menuPlanItem.Tag    = new KeyValuePair <Plan, SkillLevel>(plan,
                                                                                  new SkillLevel(queuedSkill.Skill, level));
                    });

                    ToolStripMenuItem menuLevel = tempMenuLevel;
                    tempMenuLevel = null;

                    tsmiAddSkill.DropDownItems.Add(menuLevel);
                }
                finally
                {
                    tempMenuLevel?.Dispose();
                }
            }
        }
Пример #23
0
        public static void SkillQueueTest()
        {
            var url = new Uri("https://api.eveonline.com/char/SkillQueue.xml.aspx");

            const int characterId = 123456;

            Dictionary <string, string> data = ApiTestHelpers.GetBaseTestParams();

            data.Add(ApiConstants.CharacterId, characterId.ToString(CultureInfo.InvariantCulture));

            IHttpRequestProvider mockProvider = MockRequests.GetMockedProvider(url, data,
                                                                               ApiTestHelpers.GetXmlData("TestData\\Api\\SkillQueue.xml"));

            using (
                var client = new EveAPI(ApiTestHelpers.EveServiceApiHost, ApiTestHelpers.GetNullCacheProvider(),
                                        mockProvider))
            {
                Task <EveServiceResponse <IEnumerable <QueuedSkill> > > task =
                    client.Character.SkillQueueAsync(ApiTestHelpers.KeyIdValue, ApiTestHelpers.VCodeValue, characterId);
                task.Wait();
                ApiTestHelpers.BasicSuccessResultValidations(task);

                Assert.IsNotNull(task.Result);
                Assert.IsNotEmpty(task.Result.ResultData);
                List <QueuedSkill> skillQueue = task.Result.ResultData.ToList();
                Assert.AreEqual(2, skillQueue.Count);

                QueuedSkill skill = skillQueue[1];

                Assert.AreEqual(2, skill.QueuePosition);
                Assert.AreEqual(20533, skill.TypeId);
                Assert.AreEqual(4, skill.Level);
                Assert.AreEqual(112000, skill.StartSP);
                Assert.AreEqual(633542, skill.EndSP);
                Assert.AreEqual(new DateTimeOffset(2009, 03, 18, 15, 19, 21, TimeSpan.Zero), skill.StartTime);
                Assert.AreEqual(new DateTimeOffset(2009, 03, 30, 03, 16, 14, TimeSpan.Zero), skill.EndTime);
            }
        }
Пример #24
0
        /// <summary>
        /// Handles the MouseDown event of the lbSkills control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.MouseEventArgs"/> instance containing the event data.</param>
        private void lbSkillsQueue_MouseDown(object sender, MouseEventArgs e)
        {
            // Retrieve the item at the given point and quit if none
            int index = lbSkillsQueue.IndexFromPoint(e.Location);

            if (index < 0 || index >= lbSkillsQueue.Items.Count)
            {
                return;
            }

            QueuedSkill item = lbSkillsQueue.Items[index] as QueuedSkill;

            // Beware, this last index may actually means a click in the whitespace at the bottom
            // Let's deal with this special case
            if (index == lbSkillsQueue.Items.Count - 1)
            {
                Rectangle itemRect = lbSkillsQueue.GetItemRectangle(index);
                if (!itemRect.Contains(e.Location))
                {
                    item = null;
                }
            }

            if (e.Button != MouseButtons.Right)
            {
                return;
            }

            // Right click for skills below lv5 : we display a context menu to plan higher levels
            lbSkillsQueue.Cursor = Cursors.Default;

            // Set the selected item
            m_selectedSkill = item;

            // Display the context menu
            contextMenuStrip.Show(lbSkillsQueue, e.Location);
        }
Пример #25
0
        /// <summary>
        /// Draws the list item for the given skill
        /// </summary>
        /// <param name="skill"></param>
        /// <param name="e"></param>
        public void DrawItem(QueuedSkill skill, DrawItemEventArgs e)
        {
            Graphics g = e.Graphics;

            // Draw background
            if (e.Index % 2 == 0)
            {
                // Not in training - odd
                g.FillRectangle(Brushes.LightGray, e.Bounds);
            }
            else
            {
                // Not in training - even
                g.FillRectangle(Brushes.White, e.Bounds);
            }


            // Measure texts
            var format = TextFormatFlags.NoPadding | TextFormatFlags.NoClipping;

            double percentCompleted       = 0;
            int    skillPoints            = (skill.Skill == null ? 0 : skill.Skill.SkillPoints);
            int    skillPointsToNextLevel = (skill.Skill == null
                                              ? 0
                                              : skill.Skill.StaticData.GetPointsRequiredForLevel(
                                                 Math.Min(skill.Level, 5)));

            if (skill.Skill != null && skill.Level == skill.Skill.Level + 1)
            {
                percentCompleted = skill.Skill.PercentCompleted;
            }

            if (skill.Skill != null && skill.Level > skill.Skill.Level + 1)
            {
                skillPoints = skill.CurrentSP;
            }

            string rankText = String.Format(CultureConstants.DefaultCulture, " (Rank {0})",
                                            (skill.Skill == null ? 0 : skill.Skill.Rank));
            string spText = String.Format(CultureConstants.DefaultCulture, "SP: {0:#,##0}/{1:#,##0}", skillPoints,
                                          skillPointsToNextLevel);
            string levelText = String.Format(CultureConstants.DefaultCulture, "Level {0}", skill.Level);
            string pctText   = String.Format(CultureConstants.DefaultCulture, "{0}% Done", Math.Floor(percentCompleted));

            Size skillNameSize = TextRenderer.MeasureText(g, skill.SkillName, m_boldSkillsQueueFont, Size.Empty, format);
            Size rankTextSize  = TextRenderer.MeasureText(g, rankText, m_skillsQueueFont, Size.Empty, format);
            Size levelTextSize = TextRenderer.MeasureText(g, levelText, m_skillsQueueFont, Size.Empty, format);
            Size spTextSize    = TextRenderer.MeasureText(g, spText, m_skillsQueueFont, Size.Empty, format);
            Size pctTextSize   = TextRenderer.MeasureText(g, pctText, m_skillsQueueFont, Size.Empty, format);


            // Draw texts
            Color highlightColor = Color.Black;

            TextRenderer.DrawText(g, skill.SkillName, m_boldSkillsQueueFont,
                                  new Rectangle(e.Bounds.Left + PadLeft, e.Bounds.Top + PadTop,
                                                skillNameSize.Width + PadLeft, skillNameSize.Height), highlightColor);
            TextRenderer.DrawText(g, rankText, m_skillsQueueFont,
                                  new Rectangle(e.Bounds.Left + PadLeft + skillNameSize.Width, e.Bounds.Top + PadTop,
                                                rankTextSize.Width + PadLeft, rankTextSize.Height), highlightColor);
            TextRenderer.DrawText(g, spText, m_skillsQueueFont,
                                  new Rectangle(e.Bounds.Left + PadLeft,
                                                e.Bounds.Top + PadTop + skillNameSize.Height + LineVPad,
                                                spTextSize.Width + PadLeft, spTextSize.Height), highlightColor);


            // Boxes
            g.DrawRectangle(Pens.Black,
                            new Rectangle(e.Bounds.Right - BoxWidth - PadRight, e.Bounds.Top + PadTop, BoxWidth,
                                          BoxHeight));

            int levelBoxWidth = (BoxWidth - 4 - 3) / 5;

            for (int level = 1; level <= 5; level++)
            {
                var brect =
                    new Rectangle(e.Bounds.Right - BoxWidth - PadRight + 2 + (levelBoxWidth * (level - 1)) + (level - 1),
                                  e.Bounds.Top + PadTop + 2, levelBoxWidth, BoxHeight - 3);

                if (skill.Skill != null && level <= skill.Skill.Level)
                {
                    g.FillRectangle(Brushes.Black, brect);
                }
                else
                {
                    g.FillRectangle(Brushes.DarkGray, brect);
                }

                // Color indicator for a queued level
                SkillQueue skillQueue = m_ccpCharacter.SkillQueue;
                if (skill.Skill != null)
                {
                    Brush brush = (Settings.UI.SafeForWork ? Brushes.Gray : Brushes.RoyalBlue);

                    foreach (QueuedSkill qskill in skillQueue)
                    {
                        if ((!skill.Skill.IsTraining && skill == qskill && level == qskill.Level) ||
                            (skill == qskill && level <= qskill.Level && level > skill.Skill.Level &&
                             percentCompleted == 0))
                        {
                            g.FillRectangle(brush, brect);
                        }

                        // Blinking indicator of skill level in training
                        if (skill.Skill.IsTraining && skill == qskill && level == skill.Level &&
                            percentCompleted > 0)
                        {
                            if (m_count == 0)
                            {
                                g.FillRectangle(Brushes.White, brect);
                            }

                            if (m_count == 1)
                            {
                                m_count = -1;
                            }

                            m_count++;
                        }
                    }
                }
            }

            // Draw progression bar
            g.DrawRectangle(Pens.Black,
                            new Rectangle(e.Bounds.Right - BoxWidth - PadRight,
                                          e.Bounds.Top + PadTop + BoxHeight + BoxVPad, BoxWidth, LowerBoxHeight));

            var pctBarRect = new Rectangle(e.Bounds.Right - BoxWidth - PadRight + 2,
                                           e.Bounds.Top + PadTop + BoxHeight + BoxVPad + 2,
                                           BoxWidth - 3, LowerBoxHeight - 3);

            g.FillRectangle(Brushes.DarkGray, pctBarRect);
            var fillWidth = (int)(pctBarRect.Width * (percentCompleted / 100));

            if (fillWidth > 0)
            {
                var fillRect = new Rectangle(pctBarRect.X, pctBarRect.Y, fillWidth, pctBarRect.Height);
                g.FillRectangle(Brushes.Black, fillRect);
            }


            // Draw level and percent texts
            TextRenderer.DrawText(g, levelText, m_skillsQueueFont, new Rectangle(
                                      e.Bounds.Right - BoxWidth - PadRight - BoxHPad -
                                      levelTextSize.Width,
                                      e.Bounds.Top + PadTop,
                                      levelTextSize.Width + PadRight,
                                      levelTextSize.Height), Color.Black);

            TextRenderer.DrawText(g, pctText, m_skillsQueueFont, new Rectangle(
                                      e.Bounds.Right - BoxWidth - PadRight - BoxHPad -
                                      pctTextSize.Width,
                                      e.Bounds.Top + PadTop + levelTextSize.Height +
                                      LineVPad, pctTextSize.Width + PadRight,
                                      pctTextSize.Height), Color.Black);

            // Draw the queue color bar
            DrawQueueColorBar(g, e);
        }
Пример #26
0
        /// <summary>
        /// Paints the current character's training informations, this is the regular painting operation.
        /// </summary>
        private void PaintsCharacter()
        {
            m_lcdLines.Clear();

            if (!MonitoredCharacters.Any())
            {
                m_lcdLines.Add(new LcdLine("No CCP Characters To Display", m_defaultFont));
                RenderLines();
                UpdateLcdDisplay();
                return;
            }

            if (CycleSkillQueueTime)
            {
                if (TimeSpan.FromTicks(DateTime.Now.Ticks - m_cycleQueueInfoTime.Ticks).TotalSeconds > CycleCompletionInterval)
                {
                    m_cycleQueueInfoTime     = DateTime.Now;
                    m_showingCycledQueueInfo = !m_showingCycledQueueInfo;
                }
            }

            if (CurrentCharacter == null)
            {
                return;
            }

            m_lcdLines.Add(new LcdLine(CurrentCharacter.AdornedName, m_defaultFont));

            QueuedSkill queuedSkill = CurrentCharacter.SkillQueue.CurrentlyTraining;

            if (CurrentCharacter.SkillQueue.IsTraining)
            {
                if (m_showingCycledQueueInfo)
                {
                    if (CurrentCharacter.SkillQueue.LessThanWarningThreshold)
                    {
                        // Place holder for skill queue training time rendering
                        m_lcdLines.Add(new LcdLine(" ", m_defaultFont));
                    }
                    else if (CurrentCharacter.SkillQueue.Count > 1)
                    {
                        // If more then one skill is in queue, show queue finish time
                        string endTimeText = CurrentCharacter.SkillQueue.EndTime
                                             .Subtract(DateTime.UtcNow).ToDescriptiveText(DescriptiveTextOptions.SpaceBetween);
                        m_lcdLines.Add(new LcdLine($"Queue ends in {endTimeText}", m_defaultFont));
                    }
                    else
                    {
                        // Show the skill in training
                        m_lcdLines.Add(new LcdLine($"{queuedSkill}", m_defaultFont));
                    }
                }
                else
                {
                    // Show the skill in training
                    m_lcdLines.Add(new LcdLine($"{queuedSkill}", m_defaultFont));
                }

                m_lcdLines.Add(new LcdLine(queuedSkill.EndTime.Subtract(DateTime.UtcNow).ToDescriptiveText(
                                               DescriptiveTextOptions.SpaceBetween), m_defaultFont));
            }
            else
            {
                if (CurrentCharacter.SkillQueue.IsPaused)
                {
                    m_lcdLines.Add(new LcdLine($"{queuedSkill}", m_defaultFont));
                    m_lcdLines.Add(new LcdLine("Skill Training Is Paused", m_defaultFont));
                }
                else
                {
                    m_lcdLines.Add(new LcdLine("No Skill In Training", m_defaultFont));
                    m_lcdLines.Add(new LcdLine("Skill Queue Is Empty", m_defaultFont));
                }
            }

            m_lcdLines.Add(new LcdLine($"{queuedSkill?.FractionCompleted ?? 0}", m_defaultFont));

            RenderLines();
            RenderWalletBalance();
            RenderSkillQueueInfo();
            RenderCompletionTime();

            UpdateLcdDisplay();
        }
Пример #27
0
        /// <summary>
        /// Update the controls.
        /// </summary>
        private void UpdateContent()
        {
            if (!Visible)
            {
                return;
            }

            // Update character's 'Adorned Name' and 'Portrait' in case they have changed
            lblCharName.Text = Character.LabelPrefix + Character.AdornedName;
            pbCharacterPortrait.Character = Character;
            lblTotalSkillPoints.Text      = string.Format("{0:N0} SP", Character.SkillPoints);

            FormatBalance();

            CCPCharacter ccpCharacter  = Character as CCPCharacter;
            QueuedSkill  trainingSkill = Character.CurrentlyTrainingSkill;

            // Character in training ? We have labels to fill
            if (Character.IsTraining || (ccpCharacter != null && trainingSkill != null &&
                                         ccpCharacter.SkillQueue.IsPaused))
            {
                // Update the skill in training label
                lblSkillInTraining.Text = trainingSkill.ToString();
                DateTime endTime = trainingSkill.EndTime.ToLocalTime();

                // Updates the time remaining label
                lblRemainingTime.Text = (ccpCharacter != null && ccpCharacter.SkillQueue.
                                         IsPaused) ? "Paused" : trainingSkill.RemainingTime.ToDescriptiveText(
                    DescriptiveTextOptions.IncludeCommas);

                // Update the completion time
                lblCompletionTime.Text = (ccpCharacter != null && ccpCharacter.SkillQueue.
                                          IsPaused) ? string.Empty : $"{endTime:ddd} {endTime:G}";

                // Changes the completion time color on scheduling block
                string blockingEntry;
                bool   isAutoBlocking;
                bool   isBlocking = Scheduler.SkillIsBlockedAt(endTime, out blockingEntry,
                                                               out isAutoBlocking);
                lblCompletionTime.ForeColor = (m_showConflicts && isBlocking &&
                                               (ccpCharacter == null || ccpCharacter.SkillQueue.Count == 1 ||
                                                !isAutoBlocking)) ? Color.Red : m_settingsForeColor;

                // Update the skill queue training time label
                UpdateSkillQueueTrainingTime();

                // Show the training labels
                m_hasSkillInTraining        = true;
                m_hasCompletionTime         = true;
                m_hasRemainingTime          = true;
                m_hasSkillQueueTrainingTime = true;
            }
            else
            {
                // Hide the training labels
                m_hasSkillInTraining        = false;
                m_hasCompletionTime         = false;
                m_hasRemainingTime          = false;
                m_hasSkillQueueTrainingTime = false;
            }
            // Determine the character's system location
            int    locID = Character?.LastKnownLocation?.SolarSystemID ?? 0;
            string locText;

            if (locID == 0)
            {
                locText = EveMonConstants.UnknownText + " Location";
            }
            else
            {
                locText = StaticGeography.GetSolarSystemName(locID);
            }
            lblLocation.Text = locText;
            // Adjusts all the controls layout
            PerformCustomLayout(m_isTooltip);
        }
Пример #28
0
        /// <summary>
        /// Draws the queue color bar.
        /// </summary>
        /// <param name="skill">The skill.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.DrawItemEventArgs"/> instance containing the event data.</param>
        private void DrawQueueColorBar(QueuedSkill skill, DrawItemEventArgs e)
        {
            Graphics g = e.Graphics;

            // Draw the background
            Rectangle qBarRect = new Rectangle(e.Bounds.Left, GetItemHeight - LowerBoxHeight, e.Bounds.Width, LowerBoxHeight);
            g.FillRectangle(Brushes.DimGray, qBarRect);

            double oneDaySkillQueueWidth = Character.SkillQueue.GetOneDaySkillQueueWidth(qBarRect.Width);
            IList<RectangleF> skillRects = Character.SkillQueue.GetSkillRects(skill, qBarRect.Width, LowerBoxHeight - 1).ToList();
            Brush lessThanDayBrush = Settings.UI.SafeForWork ? Brushes.LightGray : Brushes.Khaki;
            Brush moreThanDayBrush = Settings.UI.SafeForWork ? Brushes.DarkGray : Brushes.CornflowerBlue;
            RectangleF skillRectFirst = skillRects.First();

            // Skill starts before the 24h marker
            if (skillRectFirst.X < oneDaySkillQueueWidth)
            {
                // Iterate only through rectangles with width
                foreach (RectangleF skillRect in skillRects.Skip(1).Where(rect => rect.Width > 0))
                {
                    Brush brush = oneDaySkillQueueWidth - skillRect.X <= 0 ? moreThanDayBrush : lessThanDayBrush;

                    g.FillRectangle(brush,
                        new RectangleF(skillRect.X, GetItemHeight - LowerBoxHeight, skillRect.Width, skillRect.Height));
                }
                return;
            }

            g.FillRectangle(moreThanDayBrush,
                new RectangleF(skillRectFirst.X, GetItemHeight - LowerBoxHeight, skillRectFirst.Width, skillRectFirst.Height));
        }
Пример #29
0
        /// <summary>
        /// Gets the tooltip text for the given skill
        /// </summary>
        /// <param name="skill"></param>
        private string GetTooltip(QueuedSkill skill)
        {
            if (skill.Skill == null)
            {
                return(String.Empty);
            }

            int    sp               = skill.Skill.SkillPoints;
            int    nextLevel        = Math.Min(5, skill.Level);
            double percentCompleted = 0;

            if (skill.Level == skill.Skill.Level + 1)
            {
                percentCompleted = skill.Skill.PercentCompleted;
            }

            if (skill.Level > skill.Skill.Level + 1)
            {
                sp = skill.CurrentSP;
            }

            int      nextLevelSP        = skill.Skill.StaticData.GetPointsRequiredForLevel(nextLevel);
            int      pointsLeft         = nextLevelSP - sp;
            TimeSpan timeSpanFromPoints = skill.Skill.GetTimeSpanForPoints(pointsLeft);
            string   remainingTimeText  = timeSpanFromPoints.ToDescriptiveText(
                DescriptiveTextOptions.IncludeCommas | DescriptiveTextOptions.UppercaseText);

            if (sp < skill.Skill.StaticData.GetPointsRequiredForLevel(1))
            {
                // Training hasn't got past level 1 yet
                var untrainedToolTip = new StringBuilder();
                untrainedToolTip.AppendFormat(CultureConstants.DefaultCulture, "Not yet trained to Level I ({0}%)\n",
                                              Math.Floor(percentCompleted));
                untrainedToolTip.AppendFormat(CultureConstants.DefaultCulture,
                                              "Next level I: {0:#,##0} skill points remaining\n", pointsLeft);
                untrainedToolTip.AppendFormat(CultureConstants.DefaultCulture, "Training time remaining: {0}",
                                              remainingTimeText);
                AddSkillBoilerPlate(untrainedToolTip, skill.Skill);
                return(untrainedToolTip.ToString());
            }

            // So, it's a left click on a skill, we display the tool tip
            // Currently training skill?
            if (skill.Skill.IsTraining && percentCompleted != 0)
            {
                var partiallyTrainedToolTip = new StringBuilder();
                partiallyTrainedToolTip.AppendFormat(CultureConstants.DefaultCulture, "Partially Completed ({0}%)\n",
                                                     Math.Floor(percentCompleted));
                partiallyTrainedToolTip.AppendFormat(CultureConstants.DefaultCulture,
                                                     "Training to level {0}: {1:#,##0} skill points remaining\n",
                                                     Skill.GetRomanForInt(nextLevel), pointsLeft);
                partiallyTrainedToolTip.AppendFormat(CultureConstants.DefaultCulture, "Training time remaining: {0}",
                                                     remainingTimeText);
                AddSkillBoilerPlate(partiallyTrainedToolTip, skill.Skill);
                return(partiallyTrainedToolTip.ToString());
            }

            // Currently training skill but next queued level?
            if (skill.Skill.IsTraining && percentCompleted == 0)
            {
                var partiallyTrainedToolTip = new StringBuilder();
                partiallyTrainedToolTip.AppendFormat(CultureConstants.DefaultCulture,
                                                     "Previous level not yet completed\n");
                partiallyTrainedToolTip.AppendFormat(CultureConstants.DefaultCulture,
                                                     "Queued to level {0}: {1:#,##0} skill points remaining\n",
                                                     Skill.GetRomanForInt(nextLevel), pointsLeft);
                partiallyTrainedToolTip.AppendFormat(CultureConstants.DefaultCulture, "Training time to next level: {0}",
                                                     remainingTimeText);
                AddSkillBoilerPlate(partiallyTrainedToolTip, skill.Skill);
                return(partiallyTrainedToolTip.ToString());
            }

            // Partially trained skill and not in training?
            if (skill.Skill.IsPartiallyTrained && !skill.Skill.IsTraining)
            {
                var partiallyTrainedToolTip = new StringBuilder();
                partiallyTrainedToolTip.AppendFormat(CultureConstants.DefaultCulture, "Partially Completed ({0}%)\n",
                                                     Math.Floor(percentCompleted));
                partiallyTrainedToolTip.AppendFormat(CultureConstants.DefaultCulture,
                                                     "Queued to level {0}: {1:#,##0} skill points remaining\n",
                                                     Skill.GetRomanForInt(nextLevel), pointsLeft);
                partiallyTrainedToolTip.AppendFormat(CultureConstants.DefaultCulture, "Training time remaining: {0}",
                                                     remainingTimeText);
                AddSkillBoilerPlate(partiallyTrainedToolTip, skill.Skill);
                return(partiallyTrainedToolTip.ToString());
            }

            // We've completed all the skill points for the current level
            if (!skill.Skill.IsPartiallyTrained && skill.Level != 5)
            {
                var levelCompleteToolTip = new StringBuilder();
                levelCompleteToolTip.AppendFormat(CultureConstants.DefaultCulture,
                                                  "Completed Level {0}: {1:#,##0}/{2:#,##0}\n",
                                                  Skill.GetRomanForInt(skill.Level - 1), sp, nextLevelSP);
                levelCompleteToolTip.AppendFormat(CultureConstants.DefaultCulture,
                                                  "Queued level {0}: {1:#,##0} skill points required\n",
                                                  Skill.GetRomanForInt(nextLevel), pointsLeft);
                levelCompleteToolTip.AppendFormat(CultureConstants.DefaultCulture, "Training time to next level: {0}",
                                                  remainingTimeText);
                AddSkillBoilerPlate(levelCompleteToolTip, skill.Skill);
                return(levelCompleteToolTip.ToString());
            }

            // Error in calculating SkillPoints
            var calculationErrorToolTip = new StringBuilder();

            calculationErrorToolTip.AppendLine("Partially Trained (Could not calculate all skill details)");
            calculationErrorToolTip.AppendFormat(CultureConstants.DefaultCulture,
                                                 "Next level {0}: {1:#,##0} skill points remaining\n", nextLevel,
                                                 pointsLeft);
            calculationErrorToolTip.AppendFormat(CultureConstants.DefaultCulture, "Training time remaining: {0}",
                                                 remainingTimeText);
            AddSkillBoilerPlate(calculationErrorToolTip, skill.Skill);
            return(calculationErrorToolTip.ToString());
        }
Пример #30
0
        /// <summary>
        /// Builds the context menu for the selected queued skill.
        /// </summary>
        /// <param name="queuedSkill">The queued skill.</param>
        private void BuildContextMenu(QueuedSkill queuedSkill)
        {
            if (m_selectedSkill == null)
                return;

            tsmiAddSkill.DropDownItems.Clear();

            // Reset the menu
            tsmiAddSkill.Text = $"Add {queuedSkill.Skill.Name}";

            // Build the level options
            for (Int64 level = queuedSkill.Level; level <= 5; level++)
            {
                ToolStripMenuItem tempMenuLevel = null;
                try
                {
                    tempMenuLevel = new ToolStripMenuItem($"Level {Skill.GetRomanFromInt(level)} to");

                    Character.Plans.AddTo(tempMenuLevel.DropDownItems,
                        (menuPlanItem, plan) =>
                        {
                            menuPlanItem.Click += menuPlanItem_Click;
                            menuPlanItem.Tag = new KeyValuePair<Plan, SkillLevel>(plan, new SkillLevel(queuedSkill.Skill, level));
                        });

                    ToolStripMenuItem menuLevel = tempMenuLevel;
                    tempMenuLevel = null;

                    tsmiAddSkill.DropDownItems.Add(menuLevel);
                }
                finally
                {
                    tempMenuLevel?.Dispose();
                }
            }
        }
Пример #31
0
        /// <summary>
        /// Draws the boxes.
        /// </summary>
        /// <param name="fractionCompleted">The fraction completed.</param>
        /// <param name="skill">The skill.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.DrawItemEventArgs"/> instance containing the event data.</param>
        private void DrawBoxes(double fractionCompleted, QueuedSkill skill, DrawItemEventArgs e)
        {
            Graphics g = e.Graphics;

            g.DrawRectangle(Pens.Black,
                new Rectangle(e.Bounds.Right - BoxWidth - PadRight, e.Bounds.Top + PadTop, BoxWidth,
                    BoxHeight));

            const int LevelBoxWidth = (BoxWidth - 4 - 3) / 5;
            for (int level = 1; level <= 5; level++)
            {
                Rectangle brect =
                    new Rectangle(e.Bounds.Right - BoxWidth - PadRight + 2 + LevelBoxWidth * (level - 1) + (level - 1),
                        e.Bounds.Top + PadTop + 2, LevelBoxWidth, BoxHeight - 3);

                // Box color
                g.FillRectangle(skill.Skill != null && level < skill.Level ? Brushes.Black : Brushes.DarkGray, brect);

                if (skill.Skill == null)
                    continue;

                foreach (QueuedSkill qskill in Character.SkillQueue)
                {
                    if ((!qskill.IsTraining && skill == qskill && level == qskill.Level)
                        || (skill == qskill && level <= qskill.Level && level > skill.Skill.Level
                            && Math.Abs(fractionCompleted) < double.Epsilon))
                    {
                        g.FillRectangle(Brushes.RoyalBlue, brect);
                    }

                    // Blinking indicator of skill level in training
                    if (!qskill.IsTraining || skill != qskill || level != skill.Level ||
                        Math.Abs(fractionCompleted) < double.Epsilon)
                    {
                        continue;
                    }

                    if (m_blinkAction == BlinkAction.Blink)
                        g.FillRectangle(Brushes.RoyalBlue, brect);

                    m_blinkAction = m_blinkAction == BlinkAction.Reset
                        ? BlinkAction.Blink
                        : BlinkAction.Stop;
                }
            }
        }
Пример #32
0
        /// <summary>
        /// Gets the tooltip text for the given skill
        /// </summary>
        /// <param name="skill"></param>
        private static string GetTooltip(QueuedSkill skill)
        {
            if (skill.Skill == null)
                return String.Empty;

            Int64 sp = skill.Level > skill.Skill.Level + 1 ? skill.CurrentSP : skill.Skill.SkillPoints;
            Int32 nextLevel = Math.Min(5, skill.Level);
            Double fractionCompleted = skill.FractionCompleted;
            Int64 nextLevelSP = skill.Skill == Skill.UnknownSkill
                ? skill.EndSP
                : skill.Skill.StaticData.GetPointsRequiredForLevel(nextLevel);
            Int64 pointsLeft = nextLevelSP - sp;
            TimeSpan timeSpanFromPoints = skill.Skill == Skill.UnknownSkill
                ? skill.EndTime.Subtract(DateTime.UtcNow)
                : skill.Skill.GetTimeSpanForPoints(pointsLeft);
            string remainingTimeText = timeSpanFromPoints.ToDescriptiveText(
                DescriptiveTextOptions.IncludeCommas | DescriptiveTextOptions.UppercaseText);

            if (sp < skill.Skill.StaticData.GetPointsRequiredForLevel(1))
            {
                // Training hasn't got past level 1 yet
                StringBuilder untrainedToolTip = new StringBuilder();
                untrainedToolTip
                    .Append($"Not yet trained to Level I ({Math.Floor(fractionCompleted * 100) / 100:P0})")
                    .AppendLine()
                    .Append($"Next level I: {pointsLeft:N0} skill points remaining")
                    .AppendLine()
                    .Append($"Training time remaining: {remainingTimeText}")
                    .AppendLine();

                AddSkillBoilerPlate(untrainedToolTip, skill);

                return untrainedToolTip.ToString();
            }

            // So, it's a left click on a skill, we display the tool tip
            // Currently training skill?
            if (skill.IsTraining && fractionCompleted > 0)
            {
                StringBuilder partiallyTrainedToolTip = new StringBuilder();
                partiallyTrainedToolTip
                    .Append($"Partially Completed ({Math.Floor(fractionCompleted * 100) / 100:P0})")
                    .AppendLine()
                    .Append($"Training to level {Skill.GetRomanFromInt(nextLevel)}: {pointsLeft:N0} skill points remaining")
                    .AppendLine()
                    .Append($"Training time remaining: {remainingTimeText}")
                    .AppendLine();

                AddSkillBoilerPlate(partiallyTrainedToolTip, skill);

                return partiallyTrainedToolTip.ToString();
            }

            // Currently training skill but next queued level?
            if (skill.IsTraining && Math.Abs(fractionCompleted) < double.Epsilon)
            {
                StringBuilder partiallyTrainedToolTip = new StringBuilder();
                partiallyTrainedToolTip
                    .Append($"Previous level not yet completed")
                    .AppendLine()
                    .Append($"Queued to level {Skill.GetRomanFromInt(nextLevel)}: {pointsLeft:N0} skill points remaining")
                    .AppendLine()
                    .Append($"Training time to next level: {remainingTimeText}")
                    .AppendLine();

                AddSkillBoilerPlate(partiallyTrainedToolTip, skill);

                return partiallyTrainedToolTip.ToString();
            }

            // Partially trained skill and not in training?
            if (skill.Skill.IsPartiallyTrained && !skill.IsTraining)
            {
                StringBuilder partiallyTrainedToolTip = new StringBuilder();
                partiallyTrainedToolTip
                    .Append($"Partially Completed ({Math.Floor(fractionCompleted * 100) / 100:P0})")
                    .AppendLine()
                    .Append($"Queued to level {Skill.GetRomanFromInt(nextLevel)}: {pointsLeft:N0} skill points remaining")
                    .AppendLine()
                    .Append($"Training time remaining: {remainingTimeText}")
                    .AppendLine();

                AddSkillBoilerPlate(partiallyTrainedToolTip, skill);

                return partiallyTrainedToolTip.ToString();
            }

            // We've completed all the skill points for the current level
            if (!skill.Skill.IsPartiallyTrained && skill.Level <= 5)
            {
                StringBuilder levelCompleteToolTip = new StringBuilder();
                levelCompleteToolTip
                    .Append($"Completed Level {Skill.GetRomanFromInt(skill.Level - 1)}: {sp:N0}/{nextLevelSP:N0}")
                    .AppendLine()
                    .Append($"Queued level {Skill.GetRomanFromInt(nextLevel)}: {pointsLeft:N0} skill points required")
                    .AppendLine()
                    .Append($"Training time to next level: {remainingTimeText}")
                    .AppendLine();

                AddSkillBoilerPlate(levelCompleteToolTip, skill);

                return levelCompleteToolTip.ToString();
            }

            // Error in calculating SkillPoints
            StringBuilder calculationErrorToolTip = new StringBuilder();
            calculationErrorToolTip
                .AppendLine("Partially Trained (Could not calculate all skill details)")
                .Append($"Next level {nextLevel}: {pointsLeft:N0} skill points remaining")
                .AppendLine()
                .Append($"Training time remaining: {remainingTimeText}")
                .AppendLine();

            AddSkillBoilerPlate(calculationErrorToolTip, skill);

            return calculationErrorToolTip.ToString();
        }
Пример #33
0
 /// <summary>
 /// Adds the skill boiler plate.
 /// </summary>
 /// <param name="toolTip">The tool tip.</param>
 /// <param name="skill">The skill.</param>
 private static void AddSkillBoilerPlate(StringBuilder toolTip, QueuedSkill skill)
 {
     toolTip
         .AppendLine()
         .AppendLine(skill.Skill.Description.WordWrap(100))
         .Append($"Primary: {skill.Skill.PrimaryAttribute}, ")
         .Append($"Secondary: {skill.Skill.SecondaryAttribute} ")
         .Append($"({skill.SkillPointsPerHour} SP/Hour)");
 }
Пример #34
0
        /// <summary>
        /// On a mouse down event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lbSkills_MouseDown(object sender, MouseEventArgs e)
        {
            // Retrieve the item at the given point and quit if none
            int index = lbSkillsQueue.IndexFromPoint(e.X, e.Y);

            if (index < 0 || index >= lbSkillsQueue.Items.Count)
            {
                return;
            }

            // Beware, this last index may actually means a click in the whitespace at the bottom
            // Let's deal with this special case
            if (index == lbSkillsQueue.Items.Count - 1)
            {
                Rectangle itemRect = lbSkillsQueue.GetItemRectangle(index);
                if (!itemRect.Contains(e.Location))
                {
                    return;
                }
            }

            // Right click for skills below lv5 : we display a context menu to plan higher levels.
            m_item = lbSkillsQueue.Items[index] as QueuedSkill;
            var skill = m_item.Skill;

            if (skill != null)
            {
                if (e.Button == MouseButtons.Right)
                {
                    // "Show in Skill Explorer" menu item
                    var tmSkillExplorer = new ToolStripMenuItem("Show In Skill Explorer", CommonProperties.Resources.LeadsTo);
                    tmSkillExplorer.Click += tmSkillExplorer_Click;
                    tmSkillExplorer.Tag    = skill;

                    // Add to the context menu
                    contextMenuStripPlanPopup.Items.Clear();
                    contextMenuStripPlanPopup.Items.Add(tmSkillExplorer);

                    if (skill.Level < 5)
                    {
                        // Reset the menu.
                        var tm = new ToolStripMenuItem(String.Format(CultureConstants.DefaultCulture, "Add {0}", skill.Name));

                        // Build the level options.
                        int nextLevel = Math.Min(5, skill.Level + 1);
                        for (var level = nextLevel; level < 6; level++)
                        {
                            var menuLevel = new ToolStripMenuItem(String.Format(CultureConstants.DefaultCulture,
                                                                                "Level {0} to", Skill.GetRomanForInt(level)));
                            tm.DropDownItems.Add(menuLevel);

                            int level1 = level;
                            Character.Plans.AddTo(menuLevel.DropDownItems, (menuPlanItem, plan) =>
                            {
                                menuPlanItem.Click += menuPlanItem_Click;
                                menuPlanItem.Tag    = new Pair <Plan, SkillLevel>(
                                    plan, new SkillLevel(skill, level1));
                            });
                        }

                        // Add to the context menu
                        contextMenuStripPlanPopup.Items.Add(new ToolStripSeparator());
                        contextMenuStripPlanPopup.Items.Add(tm);
                    }

                    // Display the context menu
                    contextMenuStripPlanPopup.Show((Control)sender, new Point(e.X, e.Y));
                    return;
                }
            }

            // Non-right click or already lv5, display the tooltip
            DisplayTooltip(m_item);
        }
Пример #35
0
        /// <summary>
        /// Draws the list item for the given skill
        /// </summary>
        /// <param name="skill"></param>
        /// <param name="e"></param>
        private void DrawItem(QueuedSkill skill, DrawItemEventArgs e)
        {
            Graphics g = e.Graphics;

            // Draw background
            g.FillRectangle(e.Index % 2 == 0 ? Brushes.LightGray : Brushes.White, e.Bounds);

            // Measure texts
            const TextFormatFlags Format = TextFormatFlags.NoPadding | TextFormatFlags.NoClipping;
            bool hasSkill = (skill.Skill != null) && (skill.Skill != Skill.UnknownSkill);

            Int64 skillPoints = (skill.Skill != null) && (skill.Level > skill.Skill.Level + 1)
                ? skill.CurrentSP
                : !hasSkill
                    ? skill.StartSP
                    : skill.Skill.SkillPoints;
            Int64 skillPointsToNextLevel = !hasSkill
                ? skill.EndSP
                : skill.Skill.StaticData.GetPointsRequiredForLevel(Math.Min(skill.Level, 5));
            Int64 pointsLeft = skillPointsToNextLevel - skillPoints;
            TimeSpan timeSpanFromPoints = !hasSkill
                ? skill.EndTime.Subtract(DateTime.UtcNow)
                : skill.Skill.GetTimeSpanForPoints(pointsLeft);
            string remainingTimeText = timeSpanFromPoints.ToDescriptiveText(DescriptiveTextOptions.SpaceBetween);

            double fractionCompleted = e.Index == 0
                ? skill.FractionCompleted
                : 0d;

            string indexText = $"{e.Index + 1}. ";
            string rankText = $" (Rank {(skill.Skill == null ? 0 : skill.Rank)})";
            string spPerHourText = $" SP/Hour: {skill.SkillPointsPerHour}";
            string spText = $"SP: {skillPoints:N0}/{skillPointsToNextLevel:N0}";
            string trainingTimeText = $" Training Time: {remainingTimeText}";
            string levelText = $"Level {skill.Level}";
            string percentText = $"{Math.Floor(fractionCompleted * 100) / 100:P0} Done";

            Size indexTextSize = TextRenderer.MeasureText(g, indexText, m_boldSkillsQueueFont, Size.Empty, Format);
            Size skillNameSize = TextRenderer.MeasureText(g, skill.SkillName, m_boldSkillsQueueFont, Size.Empty, Format);
            Size rankTextSize = TextRenderer.MeasureText(g, rankText, m_skillsQueueFont, Size.Empty, Format);
            Size levelTextSize = TextRenderer.MeasureText(g, levelText, m_skillsQueueFont, Size.Empty, Format);
            Size spPerHourTextSize = TextRenderer.MeasureText(g, spPerHourText, m_skillsQueueFont, Size.Empty, Format);
            Size spTextSize = TextRenderer.MeasureText(g, spText, m_skillsQueueFont, Size.Empty, Format);
            Size ttTextSize = TextRenderer.MeasureText(g, trainingTimeText, m_skillsQueueFont, Size.Empty, Format);
            Size pctTextSize = TextRenderer.MeasureText(g, percentText, m_skillsQueueFont, Size.Empty, Format);

            // Draw texts
            Color highlightColor = Color.Black;

            // First line
            int left = e.Bounds.Left + PadLeft;
            int top = e.Bounds.Top + PadTop;
            TextRenderer.DrawText(g, indexText, m_boldSkillsQueueFont,
                new Rectangle(left, top, indexTextSize.Width + PadLeft, indexTextSize.Height), highlightColor);
            left += indexTextSize.Width;
            TextRenderer.DrawText(g, skill.SkillName, m_boldSkillsQueueFont,
                new Rectangle(left, top, skillNameSize.Width + PadLeft, skillNameSize.Height), highlightColor);
            left += skillNameSize.Width;
            TextRenderer.DrawText(g, rankText, m_skillsQueueFont,
                new Rectangle(left, top, rankTextSize.Width + PadLeft, rankTextSize.Height), highlightColor);

            // Second line
            left = e.Bounds.Left + PadLeft + indexTextSize.Width;
            top += skillNameSize.Height;
            TextRenderer.DrawText(g, spText, m_skillsQueueFont,
                new Rectangle(left, top, spTextSize.Width + PadLeft, spTextSize.Height), highlightColor);
            left += spTextSize.Width + PadLeft;
            TextRenderer.DrawText(g, spPerHourText, m_skillsQueueFont,
                new Rectangle(left, top, spPerHourTextSize.Width + PadLeft, spPerHourTextSize.Height), highlightColor);
            left += spPerHourTextSize.Width + PadLeft;
            TextRenderer.DrawText(g, trainingTimeText, m_skillsQueueFont,
                new Rectangle(left, top, ttTextSize.Width + PadLeft, ttTextSize.Height), highlightColor);

            // Boxes
            DrawBoxes(fractionCompleted, skill, e);

            // Draw progression bar
            DrawProgressionBar(fractionCompleted, e);

            // Draw level and percent texts
            TextRenderer.DrawText(g, levelText, m_skillsQueueFont,
                new Rectangle(e.Bounds.Right - BoxWidth - PadRight - BoxHPad - levelTextSize.Width,
                    e.Bounds.Top + PadTop, levelTextSize.Width + PadRight, levelTextSize.Height), highlightColor);
            TextRenderer.DrawText(g, percentText, m_skillsQueueFont,
                new Rectangle(e.Bounds.Right - BoxWidth - PadRight - BoxHPad - pctTextSize.Width,
                    e.Bounds.Top + PadTop + levelTextSize.Height, pctTextSize.Width + PadRight, pctTextSize.Height), highlightColor);

            // Draw the queue color bar
            DrawQueueColorBar(skill, e);
        }
Пример #36
0
        /// <summary>
        /// Handles the MouseDown event of the lbSkills control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.MouseEventArgs"/> instance containing the event data.</param>
        private void lbSkillsQueue_MouseDown(object sender, MouseEventArgs e)
        {
            // Retrieve the item at the given point and quit if none
            int index = lbSkillsQueue.IndexFromPoint(e.Location);
            if (index < 0 || index >= lbSkillsQueue.Items.Count)
                return;

            QueuedSkill item = lbSkillsQueue.Items[index] as QueuedSkill;

            // Beware, this last index may actually means a click in the whitespace at the bottom
            // Let's deal with this special case
            if (index == lbSkillsQueue.Items.Count - 1)
            {
                Rectangle itemRect = lbSkillsQueue.GetItemRectangle(index);
                if (!itemRect.Contains(e.Location))
                    item = null;
            }

            if (e.Button != MouseButtons.Right)
                return;

            // Right click for skills below lv5 : we display a context menu to plan higher levels
            lbSkillsQueue.Cursor = Cursors.Default;

            // Set the selected item
            m_selectedSkill = item;

            // Display the context menu
            contextMenuStrip.Show(lbSkillsQueue, e.Location);
        }
Пример #37
0
        /// <summary>
        /// Displays the tooltip for the given skill.
        /// </summary>
        /// <param name="skill"></param>
        private void DisplayTooltip(QueuedSkill skill)
        {
            if (skill == null)
                return;

            if (ttToolTip.Active && m_lastTooltipItem != null && m_lastTooltipItem == skill)
                return;

            m_lastTooltipItem = skill;

            ttToolTip.Active = false;
            ttToolTip.SetToolTip(lbSkillsQueue, GetTooltip(skill));
            ttToolTip.Active = true;
        }
Пример #38
0
        /// <summary>
        /// Gets the tooltip text for the given skill
        /// </summary>
        /// <param name="skill"></param>
        private static string GetTooltip(QueuedSkill skill)
        {
            if (skill.Skill == null)
            {
                return(string.Empty);
            }

            long   sp                = skill.Level > skill.Skill.Level + 1 ? skill.CurrentSP : skill.Skill.SkillPoints;
            Int32  nextLevel         = Math.Min(5, skill.Level);
            Double fractionCompleted = skill.FractionCompleted;
            long   nextLevelSP       = skill.Skill == Skill.UnknownSkill
                ? skill.EndSP
                : skill.Skill.StaticData.GetPointsRequiredForLevel(nextLevel);
            long     pointsLeft         = nextLevelSP - sp;
            TimeSpan timeSpanFromPoints = skill.Skill == Skill.UnknownSkill
                ? skill.EndTime.Subtract(DateTime.UtcNow)
                : skill.Skill.GetTimeSpanForPoints(pointsLeft);
            string remainingTimeText = timeSpanFromPoints.ToDescriptiveText(
                DescriptiveTextOptions.IncludeCommas | DescriptiveTextOptions.UppercaseText);

            if (sp < skill.Skill.StaticData.GetPointsRequiredForLevel(1))
            {
                // Training hasn't got past level 1 yet
                StringBuilder untrainedToolTip = new StringBuilder();
                untrainedToolTip
                .Append($"Not yet trained to Level I ({Math.Floor(fractionCompleted * 100) / 100:P0})")
                .AppendLine()
                .Append($"Next level I: {pointsLeft:N0} skill points remaining")
                .AppendLine()
                .Append($"Training time remaining: {remainingTimeText}")
                .AppendLine();

                AddSkillBoilerPlate(untrainedToolTip, skill);

                return(untrainedToolTip.ToString());
            }

            // So, it's a left click on a skill, we display the tool tip
            // Currently training skill?
            if (skill.IsTraining && fractionCompleted > 0)
            {
                StringBuilder partiallyTrainedToolTip = new StringBuilder();
                partiallyTrainedToolTip
                .Append($"Partially Completed ({Math.Floor(fractionCompleted * 100) / 100:P0})")
                .AppendLine()
                .Append($"Training to level {Skill.GetRomanFromInt(nextLevel)}: {pointsLeft:N0} skill points remaining")
                .AppendLine()
                .Append($"Training time remaining: {remainingTimeText}")
                .AppendLine();

                AddSkillBoilerPlate(partiallyTrainedToolTip, skill);

                return(partiallyTrainedToolTip.ToString());
            }

            // Currently training skill but next queued level?
            if (skill.IsTraining && Math.Abs(fractionCompleted) < double.Epsilon)
            {
                StringBuilder partiallyTrainedToolTip = new StringBuilder();
                partiallyTrainedToolTip
                .Append($"Previous level not yet completed")
                .AppendLine()
                .Append($"Queued to level {Skill.GetRomanFromInt(nextLevel)}: {pointsLeft:N0} skill points remaining")
                .AppendLine()
                .Append($"Training time to next level: {remainingTimeText}")
                .AppendLine();

                AddSkillBoilerPlate(partiallyTrainedToolTip, skill);

                return(partiallyTrainedToolTip.ToString());
            }

            // Partially trained skill and not in training?
            if (skill.Skill.IsPartiallyTrained && !skill.IsTraining)
            {
                StringBuilder partiallyTrainedToolTip = new StringBuilder();
                partiallyTrainedToolTip
                .Append($"Partially Completed ({Math.Floor(fractionCompleted * 100) / 100:P0})")
                .AppendLine()
                .Append($"Queued to level {Skill.GetRomanFromInt(nextLevel)}: {pointsLeft:N0} skill points remaining")
                .AppendLine()
                .Append($"Training time remaining: {remainingTimeText}")
                .AppendLine();

                AddSkillBoilerPlate(partiallyTrainedToolTip, skill);

                return(partiallyTrainedToolTip.ToString());
            }

            // We've completed all the skill points for the current level
            if (!skill.Skill.IsPartiallyTrained && skill.Level <= 5)
            {
                StringBuilder levelCompleteToolTip = new StringBuilder();
                levelCompleteToolTip
                .Append($"Completed Level {Skill.GetRomanFromInt(skill.Level - 1)}: {sp:N0}/{nextLevelSP:N0}")
                .AppendLine()
                .Append($"Queued level {Skill.GetRomanFromInt(nextLevel)}: {pointsLeft:N0} skill points required")
                .AppendLine()
                .Append($"Training time to next level: {remainingTimeText}")
                .AppendLine();

                AddSkillBoilerPlate(levelCompleteToolTip, skill);

                return(levelCompleteToolTip.ToString());
            }

            // Error in calculating SkillPoints
            StringBuilder calculationErrorToolTip = new StringBuilder();

            calculationErrorToolTip
            .AppendLine("Partially Trained (Could not calculate all skill details)")
            .Append($"Next level {nextLevel}: {pointsLeft:N0} skill points remaining")
            .AppendLine()
            .Append($"Training time remaining: {remainingTimeText}")
            .AppendLine();

            AddSkillBoilerPlate(calculationErrorToolTip, skill);

            return(calculationErrorToolTip.ToString());
        }