/// <summary>
        /// Constructor for use in code
        /// </summary>
        /// <param name="skill">The skill we want to analyze</param>
        /// <param name="planWindow">The plan window</param>
        public SkillExplorerWindow(Skill skill, PlanWindow planWindow)
            : this()
        {
            m_planWindow = planWindow;
            Skill = skill;

            EveClient.PlanChanged += new EventHandler<PlanChangedEventArgs>(EveClient_PlanChanged);
            EveClient.CharacterChanged += new EventHandler<CharacterChangedEventArgs>(EveClient_CharacterChanged);
        }
 /// <summary>
 /// Checks whether those prerequisites contains the provided skill, returning the need level
 /// </summary>
 /// <param name="skill"></param>
 /// <param name="neededLevel"></param>
 /// <returns></returns>
 public static bool Contains(this IEnumerable<SkillLevel> src, Skill skill, out int neededLevel)
 {
     neededLevel = 0;
     foreach (var prereq in src)
     {
         if (prereq.Skill == skill)
         {
             neededLevel = prereq.Level;
             return true;
         }
     }
     return false;
 }
Example #3
0
 /// <summary>
 /// Checks whether the given skill level can be planned. Used to enable or disable the "Plan To N" and "Remove" menu options.
 /// </summary>
 /// <param name="plan"></param>
 /// <param name="skill"></param>
 /// <param name="level">A integer between 0 (remove all entries for this skill) and 5.</param>
 /// <param name="operation"></param>
 /// <returns></returns>
 public static bool EnablePlanTo(Plan plan, Skill skill, int level)
 {
     // The entry actually wants to remove the item
     if (level == 0)
     {
         return plan.IsPlanned(skill);
     }
     // The entry is already known
     else if (skill.Level >= level)
     {
         return false;
     }
     // The entry is already planned at this very level
     else if (plan.GetPlannedLevel(skill) == level)
     {
         return false;
     }
     // Ok
     return true;
 }
Example #4
0
        /// <summary>
        /// Deserialization constructor
        /// </summary>
        /// <param name="character">The character for this training</param>
        /// <param name="serial">The serialization object for this training</param>
        /// <param name="isPaused">When true, the training is currently paused.</param>
        /// <param name="startTimeWhenPaused">Training starttime when the queue is actually paused. Indeed, in such case, CCP returns empty start and end time, so we compute a "what if we start now" scenario.</param>
        internal QueuedSkill(Character character, SerializableQueuedSkill serial, bool isPaused, ref DateTime startTimeWhenPaused)
        {
            m_owner = character;
            m_startSP = serial.StartSP;
            m_endSP = serial.EndSP;
            m_level = serial.Level;
            m_skill = character.Skills[serial.ID];

            if (!isPaused)
            {
                // Not paused, we should trust CCP
                m_startTime = serial.StartTime;
                m_endTime = serial.EndTime;
            }
            else
            {
                // StartTime and EndTime were empty on the serialization object if the skill was paused
                // So we compute a "what if we start now" scenario
                m_startTime = startTimeWhenPaused;
                if (m_skill != null)
                    startTimeWhenPaused += m_skill.GetLeftTrainingTimeForLevelOnly(m_level);
                m_endTime = startTimeWhenPaused;
            }
        }
 /// <summary>
 /// Show the given skill in the skill explorer.
 /// </summary>
 /// <param name="skill"></param>
 public void ShowSkillInExplorer(Skill skill)
 {
     var planWindow = WindowsFactory<PlanWindow>.GetByTag(m_plan);
     var skillExplorer = WindowsFactory<SkillExplorerWindow>.ShowByTag(planWindow, (window) => new SkillExplorerWindow(skill, window));
     skillExplorer.Skill = skill;
 }
        /// <summary>
        /// Sklll context menu > Show me what this skill unlocks
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tvSkills_DoubleClick(object sender, EventArgs e)
        {
            Skill skill = GetSelectedSkill();
            if (skill == null)
                return;

            Skill = skill;
        }
        /// <summary>
        /// Skill context menu > Show me what this skill unlocks.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tsSwitch_Click(object sender, EventArgs e)
        {
            Skill skill = GetSelectedSkill();
            if (skill == null)
                return;

            Skill = skill;
        }
 /// <summary>
 /// We want to go look at a skill in the history list again
 /// </summary>
 /// <param name="sender"></param>
 /// <param name="e"></param>
 private void cbHistory_SelectedIndexChanged(object sender, EventArgs e)
 {
     string skillName = cbHistory.Items[cbHistory.SelectedIndex] as string;
     Skill = m_character.Skills[skillName];
 }
        /// <summary>
        /// Gets the tooltip text for the given skill
        /// </summary>
        /// <param name="skill"></param>
        private string GetTooltip(Skill skill)
        {
            int sp = skill.SkillPoints;
            int nextLevel = Math.Min(5, skill.Level + 1);
            int nextLevelSP = skill.StaticData.GetPointsRequiredForLevel(nextLevel);
            int pointsLeft = skill.GetLeftPointsRequiredToLevel(nextLevel);
            string remainingTimeText = skill.GetLeftTrainingTimeToLevel(nextLevel).ToDescriptiveText(
                DescriptiveTextOptions.IncludeCommas | DescriptiveTextOptions.UppercaseText);

            if (sp < skill.StaticData.GetPointsRequiredForLevel(1))
            {
                // Training hasn't got past level 1 yet
                StringBuilder untrainedToolTip = new StringBuilder();
                untrainedToolTip.AppendFormat(CultureConstants.DefaultCulture,
                                        "Not yet trained to Level I ({0}%)\n", Math.Floor(skill.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);
                return untrainedToolTip.ToString();
            }

            // So, it's a left click on a skill, we display the tool tip
            // Partially trained skill ?
            if (skill.IsPartiallyTrained)
            {
                StringBuilder partiallyTrainedToolTip = new StringBuilder();
                partiallyTrainedToolTip.AppendFormat(CultureConstants.DefaultCulture,
                                            "Partially Completed ({0}%)\n", Math.Floor(skill.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);
                return partiallyTrainedToolTip.ToString();
            }

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

                // Lv 5 completed
                StringBuilder lv5ToolTip = new StringBuilder();
                lv5ToolTip.AppendFormat(CultureConstants.DefaultCulture, "Level V Complete: {0:#,##0}/{1:#,##0}\n", sp, nextLevelSP);
                lv5ToolTip.Append("No further training required\n");
                AddSkillBoilerPlate(lv5ToolTip, skill);
                return lv5ToolTip.ToString();
            }

            // Error in calculating SkillPoints
            StringBuilder calculationErrorToolTip = new StringBuilder();
            calculationErrorToolTip.AppendLine("Partially Trained (Could not cacluate 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);
            return calculationErrorToolTip.ToString();
        }
Example #10
0
        /// <summary>
        /// Gets the skill properties of a merged skill with a plan entry, if one is provided.
        /// If no plan is provided, the skill properties are returned unmodified.
        /// </summary>
        /// <param name="plan"></param>
        /// <param name="skill"></param>
        /// <returns>The skill properties after the merge</returns>
        private static SerializableCharacterSkill GetMergedSkill(Plan plan, Skill skill)
        {
            var mergedSkill = new SerializableCharacterSkill();

            mergedSkill.ID = skill.ID;
            mergedSkill.Name = skill.Name;
            mergedSkill.IsKnown = skill.IsKnown;
            mergedSkill.OwnsBook = skill.IsOwned;
            mergedSkill.Level = skill.Level;
            mergedSkill.Skillpoints = skill.SkillPoints;

            if (plan != null)
                plan.Merge(mergedSkill);
                
            return mergedSkill;
        }
Example #11
0
 /// <summary>
 /// Opens this skill in the skill browser and switches to this tab.
 /// </summary>
 /// <param name="gs"></param>
 public void ShowSkillInBrowser(Skill gs)
 {
     tabControl.SelectedTab = tpSkillBrowser;
     skillBrowser.SelectedSkill = gs;
 }
 /// <summary>
 /// Whenever the user right-click the skill tree on the left, we display the context menu.
 /// </summary>
 /// <param name="sender"></param>
 /// <param name="e"></param>
 private void skillTreeDisplay_SkillClicked(object sender, SkillClickedEventArgs e)
 {
     if (e.Button == MouseButtons.Right)
     {
         bool isPlanned = false;
         isPlanned |= PlanHelper.UpdatesRegularPlanToMenu(miPlanTo0, m_plan, e.Skill, 0);
         isPlanned |= PlanHelper.UpdatesRegularPlanToMenu(miPlanTo1, m_plan, e.Skill, 1);
         isPlanned |= PlanHelper.UpdatesRegularPlanToMenu(miPlanTo2, m_plan, e.Skill, 2);
         isPlanned |= PlanHelper.UpdatesRegularPlanToMenu(miPlanTo3, m_plan, e.Skill, 3);
         isPlanned |= PlanHelper.UpdatesRegularPlanToMenu(miPlanTo4, m_plan, e.Skill, 4);
         isPlanned |= PlanHelper.UpdatesRegularPlanToMenu(miPlanTo5, m_plan, e.Skill, 5);
         cmsSkillContext.Show(skillTreeDisplay, e.Location);
     }
     else
     {
         SelectedSkill = e.Skill;
     }
 }
        /// <summary>
        /// Updates the given "Plan to N" menu item.
        /// </summary>
        /// <param name="menu"></param>
        /// <param name="skill"></param>
        /// <param name="level"></param>
        /// <returns></returns>
        private bool UpdatePlanToMenuItem(ToolStripMenuItem menu, Skill skill, int level)
        {
            bool isKnown = (level <= skill.Level);
            bool isPlanned = m_plan.IsPlanned(skill, level);
            bool isTraining = skill.IsTraining;
            menu.Tag = skill;

            // Already planned ?
            if (isPlanned)
            {
                menu.Enabled = false;
                return true;
            }

            // Level already known
            if (isKnown)
            {
                menu.Enabled = false;
                return false;
            }

            // Output with prerequisites : "Plan to level V (5d 3h 15m)"
            menu.Enabled = true;
            return false;
        }
 /// <summary>
 /// Whenever the selection changes, we update the selected skill
 /// </summary>
 /// <param name="sender"></param>
 /// <param name="e"></param>
 private void skillSelectControl_SelectedSkillChanged(object sender, EventArgs e)
 {
     SelectedSkill = skillSelectControl.SelectedSkill;
 }
        /// <summary>
        /// Updates the skills tree.
        /// </summary>
        private void UpdateTree()
        {
            //Reset selected object
            SelectedSkill = null;

            // Update the image list choice
            int iconGroupIndex = Settings.UI.SkillBrowser.IconsGroupIndex;
            if (iconGroupIndex == 0)
                iconGroupIndex = 1;

            tvItems.ImageList = GetIconSet(iconGroupIndex);
            tvItems.ImageList.ColorDepth = ColorDepth.Depth32Bit;

            // Rebuild the nodes
            int numberOfItems = 0;
            tvItems.BeginUpdate();
            try
            {
                tvItems.Nodes.Clear();
                foreach (var group in m_skills.GroupBy(x => x.Group).ToArray().OrderBy(x => x.Key.Name))
                {
                    int index = tvItems.ImageList.Images.IndexOfKey("book");

                    TreeNode groupNode = new TreeNode()
                    {
                        Text = group.Key.Name,
                        ImageIndex = index,
                        SelectedImageIndex = index,
                        Tag = group.Key
                    };

                    // Add nodes for skills in this group
                    foreach (var skill in group)
                    {
                        // Choose image index
                        int imageIndex = -1;
                        if (skill.Level != 0)
                        {
                            imageIndex = tvItems.ImageList.Images.IndexOfKey("lvl" + skill.Level);
                        }
                        else if (skill.IsKnown)
                        {
                            imageIndex = tvItems.ImageList.Images.IndexOfKey("lvl0");
                        }
                        else if (skill.IsOwned)
                        {
                            imageIndex = tvItems.ImageList.Images.IndexOfKey("Book");
                        }
                        else if (skill.ArePrerequisitesMet) // prereqs met
                        {
                            imageIndex = tvItems.ImageList.Images.IndexOfKey("PrereqsMet");
                        }
                        else
                        {
                            imageIndex = tvItems.ImageList.Images.IndexOfKey("PrereqsNOTMet");
                        }

                        // Create node and adds it
                        TreeNode node = new TreeNode()
                        {
                            Text = String.Format("{0} ({1})", skill.Name, skill.Rank),
                            ImageIndex = imageIndex,
                            SelectedImageIndex = imageIndex,
                            Tag = skill
                        };

                        // We color some nodes
                        if (!skill.IsPublic && Settings.UI.SkillBrowser.ShowNonPublicSkills)
                            node.ForeColor = Color.DarkRed;

                        if (skill.IsPartiallyTrained && Settings.UI.PlanWindow.HighlightPartialSkills)
                            node.ForeColor = Color.Green;

                        if (skill.IsQueued && !skill.IsTraining && Settings.UI.PlanWindow.HighlightQueuedSkills)
                            node.ForeColor = Color.RoyalBlue;

                        if (skill.IsTraining)
                        {
                            node.BackColor = Color.LightSteelBlue;
                            node.ForeColor = Color.Black;
                        }

                        numberOfItems++;
                        groupNode.Nodes.Add(node);
                    }

                    // Add group when not empty
                    tvItems.Nodes.Add(groupNode);
                }
            }
            finally
            {
                tvItems.EndUpdate();
                m_allExpanded = false;

                // If the filtered set is small enough to fit all nodes on screen, call expandAll()
                if (numberOfItems < (tvItems.DisplayRectangle.Height / tvItems.ItemHeight))
                {
                    tvItems.ExpandAll();
                    m_allExpanded = true;
                }
            }
        }
 /// <summary>
 /// When the selection of the tree changes, updates the control's selection and fires the event.
 /// </summary>
 /// <param name="sender"></param>
 /// <param name="e"></param>
 private void tvSkillList_AfterSelect(object sender, TreeViewEventArgs e)
 {
     TreeNode tn = e.Node;
     Skill gs = tn.Tag as Skill;
     SelectedSkill = gs;
 }
 /// <summary>
 /// When the selection of the listview changes, updates the control's selection and fires the event.
 /// </summary>
 /// <param name="sender"></param>
 /// <param name="e"></param>
 private void lvSortedSkillList_SelectedIndexChanged(object sender, EventArgs e)
 {
     if (lvSortedSkillList.SelectedItems.Count == 0)
     {
         SelectedSkill = null;
     }
     else
     {
         ListViewItem lvi = lvSortedSkillList.SelectedItems[0];
         SelectedSkill = lvi.Tag as Skill;
     }
 }
 /// <summary>
 /// When the selection of the listbox changes, updates the control's selection and fires the event.
 /// </summary>
 /// <param name="sender"></param>
 /// <param name="e"></param>
 private void lbSearchList_SelectedIndexChanged(object sender, EventArgs e)
 {
     SelectedSkill = (lbSearchList.SelectedIndex >= 0 ? (Skill) lbSearchList.Items[lbSearchList.SelectedIndex] : null);
 }
Example #19
0
 /// <summary>
 /// Opens this skill in the skill explorer and switches to this tab.
 /// </summary>
 /// <param name="gs"></param>
 public void ShowSkillInExplorer(Skill gs)
 {
     skillBrowser.ShowSkillInExplorer(gs);
 }
Example #20
0
        /// <summary>
        /// Creates a plan entry and a list view item for it, from the given skill.
        /// </summary>
        /// <param name="gs"></param>
        /// <returns></returns>
        private ListViewItem CreatePlanItemForSkill(Skill skill)
        {
            // Gets the planned level of the skill.
            int newLevel = m_plan.GetPlannedLevel(skill) + 1;
            if (skill.Level >= newLevel)
                newLevel = skill.Level + 1;

            // Quits if already on lv5
            if (newLevel > 5)
                return null;

            // Creates the plan entry and list item for this level
            PlanEntry newEntry = new PlanEntry(m_plan, skill, newLevel);
            ListViewItem newItem = new ListViewItem(newEntry.ToString());
            newItem.Tag = newEntry;

            return newItem;
        }
Example #21
0
        /// <summary>
        /// Updates a regular "Plan to X" menu : text, tag, enable/disable.
        /// </summary>
        /// <param name="menu"></param>
        /// <param name="plan"></param>
        /// <param name="skill"></param>
        /// <param name="level"></param>
        public static bool UpdatesRegularPlanToMenu(ToolStripItem menu, Plan plan, Skill skill, int level)
        {
            if (level == 0)
            {
                menu.Text = "Remove";
            }
            else
            {
                menu.Text = "Level " + level.ToString();
            }

            menu.Enabled = EnablePlanTo(plan, skill, level);
            if (menu.Enabled)
            {
                var operation = plan.TryPlanTo(skill, level);
                menu.Tag = operation;
                if (RequiresWindow(operation))
                    menu.Text += "...";
            }

            var menuItem = menu as ToolStripMenuItem;
            if (menuItem != null)
            {
                menuItem.Checked = (plan.GetPlannedLevel(skill) == level);
            }
            return menu.Enabled;
        }
Example #22
0
 /// <summary>
 /// Checks whether the provided skill is an immediate prerequisite
 /// </summary>
 /// <param name="skill">The skill to test</param>
 /// <param name="neededLevel">When this skill is an immediate prerequisite, this parameter will held the required level</param>
 /// <returns></returns>
 public bool HasAsImmediatePrerequisite(Skill skill, out int neededLevel)
 {
     return m_prereqSkills.Contains(skill, out neededLevel);
 }
Example #23
0
 /// <summary>
 /// Constructor from the skill object
 /// </summary>
 /// <param name="skill"></param>
 /// <param name="level"></param>
 public SkillLevel(Skill skill, int level)
     : this()
 {
     this.Skill = skill;
     this.Level = level;
 }
Example #24
0
 /// <summary>
 /// Completes the initialization once all the character's skills have been initialized
 /// </summary>
 /// <param name="skills">The array of the character's skills.</param>
 internal void CompleteInitialization(Skill[] skills)
 {
     m_prereqs.AddRange(m_staticData.Prerequisites.Select(x => new SkillLevel(skills[x.Skill.ArrayIndex], x.Level)));
 }
 /// <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, Skill skill)
 {
     toolTip.Append("\n\n");
     toolTip.AppendLine(skill.DescriptionNL);
     toolTip.AppendFormat(CultureConstants.DefaultCulture, "\nPrimary: {0}, ", skill.PrimaryAttribute);
     toolTip.AppendFormat(CultureConstants.DefaultCulture, "Secondary: {0} ", skill.SecondaryAttribute);
     toolTip.AppendFormat(CultureConstants.DefaultCulture, "({0:#,##0} SP/hour)", skill.SkillPointsPerHour);
 }
        /// <summary>
        /// Draws the list item for the given skill
        /// </summary>
        /// <param name="skill"></param>
        /// <param name="e"></param>
        public void DrawItem(Skill skill, DrawItemEventArgs e)
        {
            Graphics g = e.Graphics;

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

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

            int skillPointsToNextLevel = skill.StaticData.GetPointsRequiredForLevel(Math.Min(skill.Level + 1, 5));

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

            Size skillNameSize = TextRenderer.MeasureText(g, skill.Name, m_boldSkillsFont, Size.Empty, format);
            Size rankTextSize = TextRenderer.MeasureText(g, rankText, m_skillsFont, Size.Empty, format);
            Size levelTextSize = TextRenderer.MeasureText(g, levelText, m_skillsFont, Size.Empty, format);
            Size spTextSize = TextRenderer.MeasureText(g, spText, m_skillsFont, Size.Empty, format);
            Size pctTextSize = TextRenderer.MeasureText(g, pctText, m_skillsFont, Size.Empty, format);

            // Draw texts
            Color highlightColor = Color.Black;
            if (!skill.IsKnown)
                highlightColor = Color.Red;
            if (!skill.IsPublic)
                highlightColor = Color.DarkRed;
            if (skill.ArePrerequisitesMet && skill.IsPublic && !skill.IsKnown)
                highlightColor = Color.SlateGray;
            if (Settings.UI.MainWindow.HighlightPartialSkills && skill.IsPartiallyTrained && !skill.IsTraining)
                highlightColor = Color.Green;
            if (Settings.UI.MainWindow.HighlightQueuedSkills && skill.IsQueued && !skill.IsTraining)
                highlightColor = Color.RoyalBlue;

            TextRenderer.DrawText(g, skill.Name, m_boldSkillsFont,
                                                            new Rectangle(
                                                                e.Bounds.Left + PadLeft,
                                                                e.Bounds.Top + PadTop,
                                                                skillNameSize.Width + PadLeft,
                                                                skillNameSize.Height), highlightColor);

            TextRenderer.DrawText(g, rankText, m_skillsFont,
                                                            new Rectangle(
                                                                e.Bounds.Left + PadLeft + skillNameSize.Width,
                                                                e.Bounds.Top + PadTop,
                                                                rankTextSize.Width + PadLeft,
                                                                rankTextSize.Height), highlightColor);

            TextRenderer.DrawText(g, spText, m_skillsFont,
                                                            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++)
            {
                Rectangle brect = new Rectangle(
                                    e.Bounds.Right - BoxWidth - PadRight + 2 + (levelBoxWidth * (level - 1)) + (level - 1),
                                    e.Bounds.Top + PadTop + 2, levelBoxWidth, BoxHeight - 3);

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

                // Color indicator for a queued level
                CCPCharacter ccpCharacter = Character as CCPCharacter;
                if (ccpCharacter != null)
                {
                    SkillQueue skillQueue = ccpCharacter.SkillQueue;
                    foreach (var qskill in skillQueue)
                    {
                        if ((!skill.IsTraining && skill == qskill.Skill && level == qskill.Level)
                           || (skill.IsTraining && skill == qskill.Skill && level == qskill.Level && level > skill.Level + 1))
                            g.FillRectangle(Brushes.RoyalBlue, brect);
                    }
                }

                // Blinking indicator of skill in training level
                if (skill.IsTraining && level == skill.Level + 1)
                {
                    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));

            Rectangle 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);
            int fillWidth = (int)(pctBarRect.Width * skill.FractionCompleted);
            if (fillWidth > 0)
            {
                Rectangle 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_skillsFont,
                                                        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_skillsFont,
                                                        new Rectangle(
                                                            e.Bounds.Right - BoxWidth - PadRight - BoxHPad - pctTextSize.Width,
                                                            e.Bounds.Top + PadTop + levelTextSize.Height + LineVPad,
                                                            pctTextSize.Width + PadRight, pctTextSize.Height), Color.Black);
        }