/// <summary>
        /// Generates an image of all hcunits that have supply of the currently selected vehicle.
        /// </summary>
        /// <param name="hcunits">A list of hcunits.</param>
        /// <param name="count">A list of the hcunits' supply numbers.</param>
        /// <param name="imgmap">An ImageMap on which to add tooltips & clickable regions.</param>
        /// <returns>The supply image to display.</returns>
        private Image GenerateSupplyImage(IList <HCUnit> hcunits, IList <int> count, ImageMap.ImageMap imgmap)
        {
            // calculate required dimensions

            const int lineHeight    = 14;
            int       linesRequired = 0;

            HCUnit prevDivision = null;

            foreach (HCUnit hcunit in hcunits)
            {
                linesRequired++;
                HCUnit division = hcunit.Level == HCUnitLevel.Division ? hcunit : hcunit.ParentUnit;
                if (division != prevDivision)
                {
                    linesRequired++; prevDivision = division;
                }
            }

            if (linesRequired == 0)
            {
                linesRequired = 4; // for "not available" message
            }
            int height = (linesRequired * lineHeight) + 2;
            int width  = imgmap.Width;


            // create bitmap, graphics objects

            Bitmap   bitmap = new Bitmap(width, height);
            Graphics g      = Graphics.FromImage(bitmap);

            g.SmoothingMode     = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
            g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;


            // remove previous tooltips

            imgmap.RemoveAll();


            // create graphics resources

            Brush brushMain         = new SolidBrush(Color.FromArgb(192, 192, 192));
            Brush brushHCUnitAllied = new SolidBrush(Color.FromArgb(128, 204, 255)); // light blue
            Brush brushHCUnitAxis   = new SolidBrush(Color.FromArgb(255, 178, 102)); // orange

            Font fontMain = new Font("Tahoma", 8.25F);

            StringFormat alignRight = new StringFormat {
                Alignment = StringAlignment.Far
            };
            StringFormat alignCenter = new StringFormat {
                Alignment = StringAlignment.Center
            };


            // hcunit loop

            int y = 0;

            prevDivision = null;

            for (int i = 0; i < hcunits.Count; i++)
            {
                HCUnit hcunit   = hcunits[i];
                HCUnit division = hcunit.Level == HCUnitLevel.Division ? hcunit : hcunit.ParentUnit;
                HCUnit brigade  = hcunit.Level == HCUnitLevel.Brigade  ? hcunit : hcunit.GetDummyDivHQBrigade();


                // draw division heading

                if (division != prevDivision)
                {
                    g.DrawImage(division.Country.BrigadeFlag, 0, y + 2.5F, 17.57F, 9); // 41x21
                    g.DrawString(division.Title, fontMain, brushMain, 17, y);

                    y           += lineHeight;
                    prevDivision = division;
                }


                // draw unit title

                g.FillEllipse(hcunit.Country.Side == Side.Allied ? brushHCUnitAllied : brushHCUnitAxis,
                              21, y + 4, 5, 5);
                g.DrawString(brigade.Title, fontMain, brushMain, 29, y);

                int stringWidth = (int)g.MeasureString(brigade.Title, fontMain, width - 29).Width - 4;
                imgmap.AddRectangle(Language.Common_Tooltip_GoToBrigade, new Rectangle(30, y + 2, stringWidth, 10), true, hcunit);


                // draw count

                g.DrawString(count[i].ToString(), fontMain, brushMain, 250, y, alignRight);


                y += lineHeight;
            } // end hcunit loop


            // if none available, show message

            if (hcunits.Count == 0)
            {
                g.DrawString(String.Format(Language.Equipment_NotAvailable, numSupplyCycle.Value),
                             fontMain, brushMain, pnlSupply.Width / 2, lineHeight * 2.5F, alignCenter);
            }
            // note: int division intentional

            // all done

            g.Dispose();
            return(bitmap);
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Generate a stats image for the given Country, showing production
        /// output percentages and damage/output graphs.
        /// </summary>
        /// <param name="imgmap">An ImageMap on which to add tooltips.</param>
        /// <param name="country">The reference Country.</param>
        /// <returns>The image to display.</returns>
        private Image GenerateImage(ImageMap.ImageMap imgmap, Country country)
        {
            // get sorted list of factories to display

            List <Factory> countryFactorys = new List <Factory>();

            foreach (Factory factory in game.Factories.Values)
            {
                if (factory.Country == country)
                {
                    countryFactorys.Add(factory);
                }
            }

            countryFactorys.Sort();


            // determine wether to show RDP info

            bool showRDP = true;

            if (country.RDPGoal <= 0)
            {
                showRDP = false;
            }
            else
            {
                foreach (Factory factory in countryFactorys)
                {
                    if (factory.TotalProduced == 0 || factory.Ticks.Count == 0)
                    {
                        showRDP = false;
                    }
                }
            }


            // create bitmap, graphics objects

            const int width  = 334;
            int       height = 25 * countryFactorys.Count;

            if (showRDP)
            {
                height += 20;
            }

            Bitmap   bitmap = new Bitmap(width, height);
            Graphics g      = Graphics.FromImage(bitmap);

            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

            int y = -22;

            imgmap.RemoveAll(); // remove previous tooltips


            // create graphics resources

            Brush brushHead = new SolidBrush(Color.FromArgb(224, 224, 224));
            Brush brushMain = new SolidBrush(Color.FromArgb(192, 192, 192));

            Brush brushPieLight = Brushes.CornflowerBlue;
            Brush brushPieDark  = Brushes.MediumBlue;

            Font fontMain = new Font("Tahoma", 8.25F);
            Font fontHead = new Font("Tahoma", 8.25F, FontStyle.Bold);
            Font fontRDP  = new Font("Tahoma", 7.5F);

            Pen penLine = new Pen(Color.FromArgb(75, 75, 75));

            StringFormat alignCenter = new StringFormat {
                Alignment = StringAlignment.Center
            };


            // rdp summary

            if (showRDP)
            {
                y += 20;


                // calc percent complete

                int totalPointsCampaign = 0;
                foreach (Factory factory in countryFactorys)
                {
                    totalPointsCampaign += factory.TotalProduced;
                }

                int totalPointsThisCycle = totalPointsCampaign - country.RDPPrevCyclePoints;
                if (totalPointsThisCycle > country.RDPGoal)
                {
                    totalPointsThisCycle = country.RDPGoal; // cycle finished
                }
                float percentComplete = ((float)totalPointsThisCycle / (float)country.RDPGoal) * 100;


                // calc eta

                int      totalPointsRemaining = country.RDPGoal - totalPointsThisCycle;
                DateTime cycleCompleted       = CalcRdpEstimatedCompletion(countryFactorys, totalPointsRemaining);


                // draw stuff, add tooltips

                g.DrawLine(penLine, 0, 20, 334, 20);

                if (cycleCompleted == DateTime.MaxValue) // never
                {
                    g.DrawString(String.Format(Language.FactoryStatus_RDPCycleNoEta, country.NextRDPCycle, percentComplete.ToString("0.00")),
                                 fontHead, brushMain, 167, 3, alignCenter);
                    imgmap.AddRectangle(Language.FactoryStatus_Tooltip_AllFactoriesCaptured, new Rectangle(230, 5, 90, 10));
                }
                else
                {
                    g.DrawString(String.Format(Language.FactoryStatus_RDPCycle, country.NextRDPCycle, percentComplete.ToString("0.00"), Misc.MinsUntilShort(cycleCompleted)),
                                 fontHead, brushMain, 167, 3, alignCenter);
                    imgmap.AddRectangle(String.Format(Language.FactoryStatus_Tooltip_EstimatedCompletion, cycleCompleted.ToString("ddd, d MMM, h:mm tt")),
                                        new Rectangle(230, 5, 90, 10));
                }

                imgmap.AddRectangle(String.Format(Language.FactoryStatus_Tooltip_RdpPoints, totalPointsThisCycle, country.RDPGoal),
                                    new Rectangle(110, 5, 100, 10));
            }


            // factory loop

            foreach (Factory factory in countryFactorys)
            {
                y += 25;


                // draw heading/parent chokepoint

                int x_indent = 0;
                if (factory.Owner.Side != country.Side) // not original side, indent and draw flag
                {
                    x_indent = 15;
                    g.DrawImage(factory.Owner.CountryFlag, 2F, y + 3.5F, 12, 7.125F);
                }

                string factoryName = Regex.Replace(factory.Name, @"^\S+\s", ""); // remove eg, "British "
                g.DrawString(factoryName, fontHead, brushHead, x_indent, y);
                g.DrawString(factory.ChokePoint.Name, fontMain, brushMain, x_indent + 10, y + 11);


                if (factory.Ticks.Count == 0)
                {
                    continue;                     // skip if no data
                }
                // draw rdp output percent pie & bar graph

                g.FillRectangle(brushPieDark, 137, y + 1, 6, 20);
                float rdpBarHeight = (factory.OutputPercentPastDay * 20F) / 100F;
                g.FillRectangle(brushPieLight, 137, y + 1 + 20 - rdpBarHeight, 6, rdpBarHeight);
                imgmap.AddRectangle(String.Format(Language.FactoryStatus_Tooltip_OutputDay, factory.OutputPercentPastDay), new Rectangle(137, y + 1, 6, 20));

                g.FillEllipse(brushPieDark, 147, y + 1, 20, 20);
                int angle = (int)((factory.OutputPercentCampaign * 360F) / 100F);
                g.FillPie(brushPieLight, 147, y + 1, 20, 20, -90, angle);
                imgmap.AddElipse(String.Format(Language.FactoryStatus_Tooltip_OutputCampaign, factory.OutputPercentCampaign), 157, y + 11, 10);


                // draw rdp output percentages

                g.DrawString(factory.OutputPercentPastDay + "%", fontMain, brushMain, 185, y, alignCenter);
                g.DrawString(factory.OutputPercentCampaign + "%", fontMain, brushPieLight, 185, y + 11, alignCenter);

                imgmap.AddRectangle(String.Format(Language.FactoryStatus_Tooltip_OutputDay, factory.OutputPercentPastDay), new Rectangle(171, y + 2, 29, 10));
                imgmap.AddRectangle(String.Format(Language.FactoryStatus_Tooltip_OutputCampaign, factory.OutputPercentCampaign), new Rectangle(171, y + 13, 29, 10));


                // draw damage & rdp 24-hour graph

                int rdpHeight = -1;                           // 0-5, rdp=1 => ++, rdp=0 => --

                for (int i = 0; i < factory.Ticks.Count; i++) // should be <= 96
                {
                    // damage graph

                    float damagePercent = factory.Ticks[i].Damage / 100F;
                    float barHeight     = 10F - (10F * damagePercent);

                    Pen penDamage = new Pen(Misc.BlendColour(Color.FromArgb(0, 150, 0), Color.Red, damagePercent));
                    g.DrawLine(penDamage, 204 + i, y + 12, 204 + i, y + 12 - barHeight);


                    // rdp graph

                    if (rdpHeight == -1) // first tick
                    {
                        rdpHeight = factory.Ticks[i].RDP == 0 ? 0 : 5;
                    }
                    else if (factory.Ticks[i].RDP == 0 && rdpHeight > 0)
                    {
                        rdpHeight--;
                    }
                    else if (factory.Ticks[i].RDP == 1 && rdpHeight < 5)
                    {
                        rdpHeight++;
                    }

                    g.DrawLine(Pens.Gray, 204 + i, y + 21, 204 + i, y + 21 - rdpHeight);
                    g.DrawLine(Pens.White, 204 + i, y + 21 - rdpHeight, 204 + i, y + 21 - rdpHeight - 1);
                }

                imgmap.AddRectangle(Language.FactoryStatus_Tooltip_GraphDamage, new Rectangle(204, y + 2, 96, 11));
                imgmap.AddRectangle(Language.FactoryStatus_Tooltip_GraphOutput, new Rectangle(204, y + 15, 96, 7));


                // draw damage percent, rdp flag

                g.DrawString(factory.CurrentHealth + "%", fontMain, brushMain, 317, y, alignCenter);
                imgmap.AddRectangle(String.Format(Language.FactoryStatus_Tooltip_CurrentHealth, factory.CurrentHealth), new Rectangle(303, y + 2, 29, 10));

                Brush brushRDP = new SolidBrush(factory.CurrentRDP ? Color.FromArgb(0, 150, 0) : Color.Red);
                g.DrawString("RDP", fontRDP, brushRDP, 317, y + 12, alignCenter);

                imgmap.AddRectangle((factory.CurrentRDP ? Language.FactoryStatus_Tooltip_RdpOn : Language.FactoryStatus_Tooltip_RdpOff), new Rectangle(307, y + 14, 20, 10));
            } // end factory loop


            g.Dispose();

            return(bitmap);
        }
        /// <summary>
        /// Generate an info image for the given ChokePoint, showing facility status,
        /// deployed hcunits, and linked cps.
        /// </summary>
        /// <param name="cp">The reference ChokePoint.</param>
        /// <param name="imgmap">An ImageMap on which to add tooltips & clickable regions.</param>
        /// <returns>The info image.</returns>
        private Image GenerateInfoImage(ChokePoint cp, ImageMap.ImageMap imgmap)
        {
            #region Init

            // get data

            ReadOnlyCollection <Bridge>    nearbyBridges = cp.GetNearbyBridges(game.Bridges);
            SortedList <float, ChokePoint> attackFrom    = cp.AttackFrom;


            // calculate required dimensions

            const int lineHeight    = 14;
            int       linesRequired = 5;

            if (attackFrom.Count == 1)
            {
                linesRequired += 1;
            }
            else if (attackFrom.Count > 1)
            {
                linesRequired += 1 + attackFrom.Count;
            }

            if (cp.IsContested)
            {
                linesRequired += 2; // capbar
            }
            linesRequired += 1 + cp.Facilities.Count;

            if (cp.LinkedChokePoints.Count > 0)
            {
                linesRequired += 1 + cp.LinkedChokePoints.Count;
            }

            if (cp.DeployedHCUnits.Count > 0)
            {
                linesRequired++;
                HCUnit prevDivision = null;
                foreach (HCUnit hcunit in cp.DeployedHCUnits)
                {
                    linesRequired++;
                    HCUnit division = hcunit.Level == HCUnitLevel.Division ? hcunit : hcunit.ParentUnit;
                    if (division != prevDivision)
                    {
                        linesRequired++; prevDivision = division;
                    }
                }
            }

            if (nearbyBridges.Count > 0)
            {
                linesRequired += 1 + nearbyBridges.Count;
            }

            int       height = (linesRequired * lineHeight) + 2;
            const int width  = 334;


            // create bitmap, graphics resources

            Bitmap   bitmap = new Bitmap(width, height);
            Graphics g      = Graphics.FromImage(bitmap);
            g.SmoothingMode     = SmoothingMode.HighQuality;
            g.InterpolationMode = InterpolationMode.High;

            Brush brushMain         = new SolidBrush(Color.FromArgb(192, 192, 192));
            Brush brushHead         = new SolidBrush(Color.FromArgb(224, 224, 224));
            Brush brushHCUnitAllied = new SolidBrush(Color.FromArgb(128, 204, 255)); // light blue
            Brush brushHCUnitAxis   = new SolidBrush(Color.FromArgb(255, 178, 102)); // orange

            Pen penDark = new Pen(Color.FromArgb(80, 80, 80));

            Font fontMain = new Font("Tahoma", 8.25F);
            Font fontHead = new Font("Tahoma", 8.25F, FontStyle.Bold);

            StringFormat alignRight = new StringFormat {
                Alignment = StringAlignment.Far
            };
            StringFormat formatNoWrap = new StringFormat {
                FormatFlags = StringFormatFlags.NoWrap
            };                                                                                  // prevents odd spacing when line is clipped

            DateTime oneHourAgo     = DateTime.Now.AddHours(-1);
            DateTime twelveHoursAgo = DateTime.Now.AddHours(-12);

            imgmap.RemoveAll(); // remove previous tooltips

            int   y    = 0;
            int[] xTab = new[] { 2, 15, 32, 44 };
            int   stringWidth;

            #endregion

            #region General info

            // get x position for owner/controller values

            float xFlagPos = 80; // min
            stringWidth = (int)g.MeasureString(Language.Misc_OwnedBy, fontHead).Width;
            if (xTab[0] + stringWidth + 2 > xFlagPos)
            {
                xFlagPos = xTab[0] + stringWidth + 2;
            }
            stringWidth = (int)g.MeasureString(Language.Misc_ControlledBy, fontHead).Width;
            if (xTab[0] + stringWidth + 2 > xFlagPos)
            {
                xFlagPos = xTab[0] + stringWidth + 2;
            }
            xFlagPos = (float)Math.Round(xFlagPos);


            // owner

            g.DrawString(Language.Misc_OwnedBy, fontHead, brushHead, xTab[0], y);
            g.DrawImage(cp.Owner.CountryFlag, xFlagPos, y + 3.5F, 12, 7.125F);

            string captured = null;
            if (cp.LastOwnerChanged > twelveHoursAgo) // within past 12 hours
            {
                captured = " (" + String.Format(Language.Time_CapturedMinsAgo, Misc.MinsAgoLong(cp.LastOwnerChanged)) + ")";
            }

            g.DrawString(String.Format("{0}{1}", cp.Owner, captured), fontMain, brushMain, xFlagPos + 15, y);
            y += lineHeight;


            // controller

            g.DrawString(Language.Misc_ControlledBy, fontHead, brushHead, xTab[0], y);
            g.DrawImage(cp.Controller.CountryFlag, xFlagPos, y + 3.5F, 12, 7.125F);

            string gained = null;
            if (cp.LastControllerChanged > twelveHoursAgo) // within past 12 hours
            {
                gained = " (" + String.Format(cp.Controller.Side == cp.Owner.Side ? Language.Time_RegainedMinsAgo : Language.Time_GainedMinsAgo,
                                              Misc.MinsAgoLong(cp.LastControllerChanged)) + ")";
            }

            g.DrawString(String.Format("{0}{1}", cp.Controller, gained), fontMain, brushMain, xFlagPos + 15, y);
            y += lineHeight;


            // contested

            if (cp.IsContested)
            {
                g.DrawImage(Resources.contested, xTab[1], y + 2, 13, 10.52F); // 21x17

                string since = null;
                if (cp.LastContestedChanged > twelveHoursAgo) // within past 12 hours
                {
                    since = " (" + String.Format(Language.Time_ForMins, Misc.MinsAgoLong(cp.LastContestedChanged)) + ")";
                }

                g.DrawString(Language.Misc_Contested + since, fontMain, brushMain, xTab[2], y);
            }
            else
            {
                g.DrawImage(Resources.uncontested, xTab[1], y + 2, 13, 10.52F); // 21x17

                string since = null;
                if (cp.LastContestedChanged > twelveHoursAgo) // within past 12 hours
                {
                    since = " (" + String.Format(Language.Time_ForMins, Misc.MinsAgoLong(cp.LastContestedChanged)) + ")";
                }

                g.DrawString(Language.Misc_Uncontested + since, fontMain, brushMain, xTab[2], y);
            }
            y += lineHeight;


            // attack objective

            if (cp.HasAO)
            {
                g.DrawImage(Resources.attack_objective, xTab[1], y + 2, 12, 10.29F); // 21x18

                string placed = null;
                if (cp.LastAOChanged > twelveHoursAgo) // within past 12 hours
                {
                    placed = " (" + String.Format(Language.Time_PlacedMinsAgo, Misc.MinsAgoLong(cp.LastAOChanged)) + ")";
                }
                g.DrawString(Language.TownStatus_AttackObjective + placed, fontMain, brushMain, xTab[2], y);

                string attackUnit = cp.AttackingHCUnit.Title;
                if (cp.AttackingHCUnit.DeployedChokePoint != null)
                {
                    attackUnit += " " + String.Format(Language.Misc_AtChokePoint, cp.AttackingHCUnit.DeployedChokePoint);
                }

                string aoTooltip = String.Format(Language.TownStatus_Tooltip_AOPlacedBy, attackUnit);

                imgmap.AddRectangle(aoTooltip, new Rectangle(xTab[1], y + 2, 100, 10));

                y += lineHeight;
            }
            else
            {
                g.DrawImage(Resources.no_attack_objective, xTab[1], y + 2, 12, 10.29F); // 21x18

                string removed = null;
                if (cp.LastAOChanged > twelveHoursAgo) // within past 12 hours
                {
                    removed = " (" + String.Format(Language.Time_RemovedMinsAgo, Misc.MinsAgoLong(cp.LastAOChanged)) + ")";
                }

                g.DrawString(Language.TownStatus_NoAttackObjective + removed, fontMain, brushMain, xTab[2], y);
                y += lineHeight;
            }


            // activity level

            g.DrawImage(cp.ActivityImage, xTab[1], y + 2, 12, 10.8F); // 20x18
            g.DrawString(Language.TownStatus_Activity + " " + Misc.EnumString(cp.Activity), fontMain, brushMain, xTab[2], y);
            y += lineHeight;


            // attack from

            if (attackFrom.Count > 0)
            {
                if (attackFrom.Count == 1)
                {
                    g.DrawString(Language.TownStatus_UnderAttackFrom + " " + attackFrom.Values[0].Name, fontMain, brushMain, xTab[2], y);
                    y += lineHeight;
                }
                else
                {
                    g.DrawString(Language.TownStatus_UnderAttackFrom, fontMain, brushMain, xTab[2], y);
                    y += lineHeight;

                    for (int i = attackFrom.Count - 1; i >= 0; i--)
                    {
                        g.DrawString(String.Format("{0} ({1:0}%)", attackFrom.Values[i], attackFrom.Keys[i]), fontMain, brushMain, xTab[3], y);
                        y += lineHeight;
                    }
                }
            }

            #endregion

            #region Base counts

            int countArmybase = 0, countAirbase = 0, countNavalbase = 0;

            foreach (Facility facility in cp.Facilities)
            {
                if (facility.Type == FacilityType.Armybase)
                {
                    countArmybase++;
                }
                else if (facility.Type == FacilityType.Airbase)
                {
                    countAirbase++;
                }
                else if (facility.Type == FacilityType.Navalbase)
                {
                    countNavalbase++;
                }
            }

            int yOffset = 0;
            if (countArmybase > 0)
            {
                g.DrawImage(Resources.armybase, 300, yOffset, 18, 16);
                g.DrawString("x" + countArmybase, fontMain, brushMain, 334, yOffset + 1, alignRight);
                yOffset += 18;
            }
            if (countAirbase > 0)
            {
                g.DrawImage(Resources.airbase, 300, yOffset, 18, 16);
                g.DrawString("x" + countAirbase, fontMain, brushMain, 334, yOffset + 1, alignRight);
                yOffset += 20;
            }
            if (countNavalbase > 0)
            {
                g.DrawImage(Resources.navalbase, 300, yOffset, 18, 16);
                g.DrawString("x" + countNavalbase, fontMain, brushMain, 334, yOffset + 1, alignRight);
            }

            #endregion

            #region Capbar

            if (cp.IsContested)
            {
                const int left  = 19;
                const int right = 319;
                int       range = right - left;
                int       mid   = (int)((100 - cp.PercentOwnership) * range / 100) + left;

                g.DrawImage(cp.Owner.Side == Side.Allied ? Resources.capbar_red_left : Resources.capbar_blue_left,
                            left - 4, y + 9, 4, 10);
                g.DrawImage(cp.Owner.Side == Side.Allied ? Resources.capbar_red : Resources.capbar_blue,
                            new Rectangle(left, y + 9, mid - left, 10),
                            new Rectangle(0, 0, 1, 10), GraphicsUnit.Pixel);
                g.DrawImage(cp.Owner.Side == Side.Allied ? Resources.capbar_blue : Resources.capbar_red,
                            new Rectangle(mid, y + 9, right - mid, 10),
                            new Rectangle(0, 0, 1, 10), GraphicsUnit.Pixel);
                g.DrawImage(cp.Owner.Side == Side.Allied ? Resources.capbar_blue_right : Resources.capbar_red_right,
                            right, y + 9, 4, 10);
                g.DrawImage(Resources.capbar_pointer, mid - 13, y + 6, 27, 23);

                y += lineHeight + lineHeight;
            }

            #endregion

            #region Facility list

            int maxNameWidth = 255;

            // heading(s)

            g.DrawString(Language.TownStatus_Section_Facilities, fontHead, brushHead, xTab[0], y);
            if (cp.HasAO || cp.IsContested)
            {
                g.DrawString(Language.TownStatus_Tables, fontMain, brushHead, 252, y);
                maxNameWidth = 215;
            }
            if (cp.IsContested)
            {
                g.DrawString(Language.TownStatus_HeldFor, fontMain, brushHead, 250, y, alignRight);
                maxNameWidth = 160;
            }
            g.DrawString(Language.TownStatus_Spawn, fontMain, brushHead, 297, y);


            // facility loop

            foreach (Facility facility in cp.Facilities)
            {
                y += lineHeight;


                // owner flag & facility icon

                g.DrawImage(facility.Owner.CountryFlag, xTab[1], y + 3.5F, 12, 7.125F);
                g.DrawImage(facility.Icon, xTab[2], y + 2.5F, 11, 8.94F);


                // facility name

                g.DrawString(facility.Name, fontMain, brushMain, new RectangleF(xTab[3], y, maxNameWidth, lineHeight), formatNoWrap);


                // held for column

                if (cp.IsContested)
                {
                    if (facility.LastOwnerChanged > twelveHoursAgo) // within past 12 hours
                    {
                        Brush brush;
                        if (facility.LastOwnerChanged < oneHourAgo)
                        {
                            brush = new SolidBrush(Color.FromArgb(128, 128, 128));
                        }
                        else
                        {
                            brush = new SolidBrush(Misc.GetFadedYellow(facility.LastOwnerChanged, 15, 192));
                        }

                        g.DrawString(Misc.MinsAgoShort(facility.LastOwnerChanged), fontMain, brush, 250, y, alignRight);
                        imgmap.AddRectangle(Misc.Timestamp(facility.LastOwnerChanged) + " " + String.Format(Language.TownStatus_Tooltip_CapturedBy, facility.LastCapper),
                                            new Rectangle(210, y + 2, 38, 10));
                    }
                    else
                    {
                        g.DrawLine(penDark, 209, y + 6, 247.5F, y + 6);
                    }
                }


                // table status column

                if (cp.HasAO || cp.IsContested)
                {
                    if (facility.RadioTableUp)
                    {
                        g.DrawImage(Resources.table_up, 269, y + 2, 12, 10);
                    }
                    else
                    {
                        g.DrawImage(Resources.table_down, 269, y + 2, 12, 10);
                    }
                }


                // spawnable column

                Image  spawnImage;
                string spawnTooltip;

                if (!facility.SpawnableFacility)
                {
                    spawnImage   = Resources.spawn_nonspawnable;
                    spawnTooltip = null;
                }
                else if (facility.Owner.Side == cp.Owner.Side) // friendly
                {
                    if (facility.EnemySpawnable)
                    {
                        spawnImage   = Resources.spawn_enemyspawnable;
                        spawnTooltip = Language.TownStatus_Tooltip_SpawnSpawnable;
                    }
                    else if (facility.OwnerCanSpawn)
                    {
                        spawnImage   = Resources.spawn_friendly;
                        spawnTooltip = Language.TownStatus_Tooltip_SpawnFriendly;
                    }
                    else
                    {
                        spawnImage   = Resources.spawn_closed;
                        spawnTooltip = Language.TownStatus_Tooltip_SpawnClosed;
                    }
                }
                else // enemy
                {
                    if (facility.OwnerCanSpawn)
                    {
                        spawnImage   = Resources.spawn_enemy;
                        spawnTooltip = Language.TownStatus_Tooltip_SpawnEnemy;
                    }
                    else
                    {
                        spawnImage   = Resources.spawn_closed;
                        spawnTooltip = Language.TownStatus_Tooltip_SpawnClosed;
                    }
                }

                g.DrawImage(spawnImage, 311, y + 2, 9, 9);
                if (spawnTooltip != null)
                {
                    imgmap.AddRectangle(spawnTooltip, new Rectangle(311, y + 2, 9, 9));
                }
            } // end facility loop

            #endregion

            #region Deployed hcunits

            if (cp.DeployedHCUnits.Count > 0)
            {
                int xTimeColumn = new BegmMisc.TextWidth(fontMain, 70, 230).MeasureAll(cp.DeployedHCUnits).MaxWidth + 100;

                y += lineHeight;
                g.DrawString(Language.TownStatus_Section_HCUnits, fontHead, brushHead, xTab[0], y);

                HCUnit prevDivision = null;

                foreach (HCUnit hcunit in cp.DeployedHCUnits)
                {
                    y += lineHeight;

                    HCUnit division = hcunit.Level == HCUnitLevel.Division ? hcunit : hcunit.ParentUnit;
                    HCUnit brigade  = hcunit.Level == HCUnitLevel.Brigade  ? hcunit : hcunit.GetDummyDivHQBrigade();

                    if (division != prevDivision) // draw division heading w/ tooltip
                    {
                        prevDivision = division;

                        g.DrawImage(division.Country.BrigadeFlag, xTab[1], y + 2.5F, 17.57F, 9); // 41x21
                        g.DrawString(division.Title, fontMain, brushMain, xTab[2], y);

                        string divTooltip = null;
                        if (division.IsDeployed)
                        {
                            if (division.DeployedChokePoint != cp)
                            {
                                divTooltip = String.Format(Language.Common_Tooltip_DivisionDeployed, division.DeployedChokePoint);
                            }
                        }
                        else
                        {
                            divTooltip = Language.Common_Tooltip_DivisionNotDeployed;
                        }

                        if (divTooltip != null)
                        {
                            stringWidth = (int)g.MeasureString(division.Title, fontMain, width - 32).Width - 2;
                            imgmap.AddRectangle(divTooltip, new Rectangle(xTab[2] + 1, y + 2, stringWidth, 10), true, division);
                        }

                        y += lineHeight;
                    }


                    // draw unit title, time w/ last moved tooltip

                    g.FillEllipse(hcunit.Country.Side == Side.Allied ? brushHCUnitAllied : brushHCUnitAxis,
                                  xTab[2] + 4, y + 4, 5, 5);
                    g.DrawString(brigade.Title, fontMain, brushMain, xTab[3], y);
                    if (hcunit.HasLastMoved)
                    {
                        g.DrawString(Misc.MinsAgoShort(hcunit.LastMovedTime), fontMain, brushMain, xTimeColumn, y, alignRight);
                    }

                    string brigTooltip = hcunit.Tooltip;
                    if (brigTooltip != null)
                    {
                        stringWidth = (int)g.MeasureString(brigade.Title, fontMain, width - xTab[3]).Width - 2;
                        imgmap.AddRectangle(brigTooltip, new Rectangle(xTab[3] + 1, y + 2, stringWidth, 10), true, hcunit);
                    }
                }
            }

            #endregion

            #region Linked cps/firebases

            if (cp.LinkedChokePoints.Count > 0)
            {
                // calc x position for kms column based on max town name length

                float xKmsColumn = 170; // min
                foreach (ChokePoint linkedChokePoint in cp.LinkedChokePoints)
                {
                    stringWidth = (int)g.MeasureString(linkedChokePoint.Name, fontMain).Width;
                    if (stringWidth + 100 > xKmsColumn)
                    {
                        xKmsColumn = stringWidth + 100;
                    }
                }


                y += lineHeight;
                g.DrawString(Language.TownStatus_Section_LinkedTowns, fontHead, brushHead, xTab[0], y);

                foreach (ChokePoint linkedChokePoint in cp.LinkedChokePoints)
                {
                    y += lineHeight;


                    // owner flag

                    g.DrawImage(linkedChokePoint.Owner.CountryFlag, xTab[1], y + 3.5F, 12, 7.125F);
                    imgmap.AddRectangle(String.Format(Language.Common_Tooltip_GoToMap, linkedChokePoint.Name), new Rectangle(xTab[1], y + 3, 12, 8), true, linkedChokePoint.ID);


                    // fb icon

                    Firebase linkedFirebase = cp.GetFirebaseFrom(linkedChokePoint);
                    string   fbTooltip;

                    if (linkedFirebase == null)
                    {
                        fbTooltip = String.Format(Language.TownStatus_Tooltip_FirebaseNone, cp, linkedChokePoint);
                    }
                    else // draw fb icon
                    {
                        Image fbImage;

                        if (linkedFirebase.Link.State == FirebaseState.Inactive)
                        {
                            fbImage   = Resources.facility_firebase;
                            fbTooltip = String.Format(Language.TownStatus_Tooltip_FirebaseInactive, cp, linkedChokePoint);
                        }
                        else if (linkedFirebase.IsOpen)
                        {
                            fbImage   = Resources.facility_firebase_open;
                            fbTooltip = String.Format(Language.TownStatus_Tooltip_FirebaseUp,
                                                      linkedChokePoint, cp, Misc.EnumString(linkedFirebase.Link.State).ToLower(), Misc.EnumString(linkedFirebase.Link.Side));
                        }
                        else
                        {
                            fbImage   = Resources.facility_firebase_closed;
                            fbTooltip = String.Format(Language.TownStatus_Tooltip_FirebaseDown,
                                                      linkedChokePoint, cp, Misc.EnumString(linkedFirebase.Link.State).ToLower(), Misc.EnumString(linkedFirebase.Link.Side));
                        }

                        g.DrawImage(fbImage, xTab[2], y + 2.5F, 11, 8.94F);
                    }
                    imgmap.AddRectangle(fbTooltip, new Rectangle(xTab[2], y + 2, 11, 9));


                    // linked cp name

                    g.DrawString(linkedChokePoint.Name, fontMain, brushMain, xTab[3], y);
                    stringWidth = (int)g.MeasureString(linkedChokePoint.Name, fontMain, width - xTab[3]).Width - 2;
                    imgmap.AddRectangle(String.Format(Language.Common_Tooltip_GoToStatus, linkedChokePoint.Name), new Rectangle(xTab[3] + 1, y + 2, stringWidth, 10), true, linkedChokePoint);


                    // distance

                    double distance = Misc.DistanceBetween(cp.LocationOctets, linkedChokePoint.LocationOctets) * 0.8; // x 800m / 1000m
                    g.DrawString(distance.ToString("0.0 km"), fontMain, brushMain, (int)xKmsColumn, y, alignRight);
                }
            }

            #endregion

            #region Nearby bridges

            if (nearbyBridges.Count > 0)
            {
                y += lineHeight;
                g.DrawString(Language.TownStatus_Section_Bridges, fontHead, brushHead, xTab[0], y);

                foreach (Bridge bridge in nearbyBridges)
                {
                    y += lineHeight;
                    g.DrawImage(Resources.bridge, xTab[2], y + 3, 11, 9);
                    g.DrawString(bridge.Name, fontMain, brushMain, xTab[3], y);
                }
            }

            #endregion

            g.Dispose();
            return(bitmap);
        }