CnC_Remastered_Collection

Command and Conquer: Red Alert
Log | Files | Refs | README | LICENSE

ViewTool.cs (13395B)


      1 //
      2 // Copyright 2020 Electronic Arts Inc.
      3 //
      4 // The Command & Conquer Map Editor and corresponding source code is free 
      5 // software: you can redistribute it and/or modify it under the terms of 
      6 // the GNU General Public License as published by the Free Software Foundation, 
      7 // either version 3 of the License, or (at your option) any later version.
      8 
      9 // The Command & Conquer Map Editor and corresponding source code is distributed 
     10 // in the hope that it will be useful, but with permitted additional restrictions 
     11 // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT 
     12 // distributed with this program. You should have received a copy of the 
     13 // GNU General Public License along with permitted additional restrictions 
     14 // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
     15 using MobiusEditor.Controls;
     16 using MobiusEditor.Event;
     17 using MobiusEditor.Interface;
     18 using MobiusEditor.Model;
     19 using MobiusEditor.Render;
     20 using MobiusEditor.Utility;
     21 using MobiusEditor.Widgets;
     22 using System;
     23 using System.Collections.Generic;
     24 using System.ComponentModel;
     25 using System.Drawing;
     26 using System.Drawing.Drawing2D;
     27 using System.Linq;
     28 using System.Windows.Forms;
     29 
     30 namespace MobiusEditor.Tools
     31 {
     32     public abstract class ViewTool : ITool
     33     {
     34         protected readonly IGamePlugin plugin;
     35         protected readonly Map map;
     36 
     37         protected readonly MapPanel mapPanel;
     38         protected readonly ToolStripStatusLabel statusLbl;
     39         protected readonly UndoRedoList<UndoRedoEventArgs> url;
     40         protected readonly NavigationWidget navigationWidget;
     41 
     42         protected virtual Map RenderMap => map;
     43 
     44         private MapLayerFlag layers;
     45         public MapLayerFlag Layers
     46         {
     47             get => layers;
     48             set
     49             {
     50                 if (layers != value)
     51                 {
     52                     layers = value;
     53                     Invalidate();
     54                 }
     55             }
     56         }
     57 
     58         public ViewTool(MapPanel mapPanel, MapLayerFlag layers, ToolStripStatusLabel statusLbl, IGamePlugin plugin, UndoRedoList<UndoRedoEventArgs> url)
     59         {
     60             this.layers = layers;
     61             this.plugin = plugin;
     62             this.url = url;
     63 
     64             this.mapPanel = mapPanel;
     65             this.mapPanel.PreRender += MapPanel_PreRender;
     66             this.mapPanel.PostRender += MapPanel_PostRender;
     67 
     68             this.statusLbl = statusLbl;
     69 
     70             map = plugin.Map;
     71             map.BasicSection.PropertyChanged += BasicSection_PropertyChanged;
     72 
     73             navigationWidget = new NavigationWidget(mapPanel, map.Metrics, Globals.TileSize);
     74         }
     75 
     76         protected void Invalidate()
     77         {
     78             mapPanel.Invalidate(RenderMap);
     79         }
     80 
     81         private void BasicSection_PropertyChanged(object sender, PropertyChangedEventArgs e)
     82         {
     83             switch (e.PropertyName)
     84             {
     85                 case "BasePlayer":
     86                     {
     87                         foreach (var baseBuilding in map.Buildings.OfType<Building>().Select(x => x.Occupier).Where(x => x.BasePriority >= 0))
     88                         {
     89                             mapPanel.Invalidate(map, baseBuilding);
     90                         }
     91                     }
     92                     break;
     93             }
     94         }
     95 
     96         private void MapPanel_PreRender(object sender, RenderEventArgs e)
     97         {
     98             if ((e.Cells != null) && (e.Cells.Count == 0))
     99             {
    100                 return;
    101             }
    102 
    103             PreRenderMap();
    104 
    105             using (var g = Graphics.FromImage(mapPanel.MapImage))
    106             {
    107                 if (Properties.Settings.Default.Quality > 1)
    108                 {
    109                     g.InterpolationMode = InterpolationMode.NearestNeighbor;
    110                     g.PixelOffsetMode = PixelOffsetMode.HighSpeed;
    111                 }
    112 
    113                 MapRenderer.Render(plugin.GameType, RenderMap, g, e.Cells?.Where(p => map.Metrics.Contains(p)).ToHashSet(), Layers);
    114             }
    115         }
    116 
    117         private void MapPanel_PostRender(object sender, RenderEventArgs e)
    118         {
    119             PostRenderMap(e.Graphics);
    120             navigationWidget.Render(e.Graphics);
    121         }
    122 
    123         protected virtual void PreRenderMap() { }
    124 
    125         protected virtual void PostRenderMap(Graphics graphics)
    126         {
    127             if ((Layers & MapLayerFlag.Waypoints) != MapLayerFlag.None)
    128             {
    129                 var waypointBackgroundBrush = new SolidBrush(Color.FromArgb(96, Color.Black));
    130                 var waypointBrush = new SolidBrush(Color.FromArgb(128, Color.DarkOrange));
    131                 var waypointPen = new Pen(Color.DarkOrange);
    132 
    133                 foreach (var waypoint in map.Waypoints)
    134                 {
    135                     if (waypoint.Cell.HasValue)
    136                     {
    137                         var x = waypoint.Cell.Value % map.Metrics.Width;
    138                         var y = waypoint.Cell.Value / map.Metrics.Width;
    139 
    140                         var location = new Point(x * Globals.TileWidth, y * Globals.TileHeight);
    141                         var textBounds = new Rectangle(location, Globals.TileSize);
    142 
    143                         graphics.FillRectangle(waypointBackgroundBrush, textBounds);
    144                         graphics.DrawRectangle(waypointPen, textBounds);
    145 
    146                         StringFormat stringFormat = new StringFormat
    147                         {
    148                             Alignment = StringAlignment.Center,
    149                             LineAlignment = StringAlignment.Center
    150                         };
    151 
    152                         var text = waypoint.Name.ToString();
    153                         var font = graphics.GetAdjustedFont(text, SystemFonts.DefaultFont, textBounds.Width, 24 / Globals.TileScale, 48 / Globals.TileScale, true);
    154                         graphics.DrawString(text.ToString(), font, waypointBrush, textBounds, stringFormat);
    155                     }
    156                 }
    157             }
    158 
    159             if ((Layers & MapLayerFlag.TechnoTriggers) != MapLayerFlag.None)
    160             {
    161                 var technoTriggerBackgroundBrush = new SolidBrush(Color.FromArgb(96, Color.Black));
    162                 var technoTriggerBrush = new SolidBrush(Color.LimeGreen);
    163                 var technoTriggerPen = new Pen(Color.LimeGreen);
    164 
    165                 foreach (var (cell, techno) in map.Technos)
    166                 {
    167                     var location = new Point(cell.X * Globals.TileWidth, cell.Y * Globals.TileHeight);
    168 
    169                     (string trigger, Rectangle bounds)[] triggers = null;
    170                     if (techno is Terrain terrain)
    171                     {
    172                         triggers = new (string, Rectangle)[] { (terrain.Trigger, new Rectangle(location, terrain.Type.RenderSize)) };
    173                     }
    174                     else if (techno is Building building)
    175                     {
    176                         var size = new Size(building.Type.Size.Width * Globals.TileWidth, building.Type.Size.Height * Globals.TileHeight);
    177                         triggers = new (string, Rectangle)[] { (building.Trigger, new Rectangle(location, size)) };
    178                     }
    179                     else if (techno is Unit unit)
    180                     {
    181                         triggers = new (string, Rectangle)[] { (unit.Trigger, new Rectangle(location, Globals.TileSize)) };
    182                     }
    183                     else if (techno is InfantryGroup infantryGroup)
    184                     {
    185                         List<(string, Rectangle)> infantryTriggers = new List<(string, Rectangle)>();
    186                         for (var i = 0; i < infantryGroup.Infantry.Length; ++i)
    187                         {
    188                             var infantry = infantryGroup.Infantry[i];
    189                             if (infantry == null)
    190                             {
    191                                 continue;
    192                             }
    193 
    194                             var size = Globals.TileSize;
    195                             var offset = Size.Empty;
    196                             switch ((InfantryStoppingType)i)
    197                             {
    198                                 case InfantryStoppingType.UpperLeft:
    199                                     offset.Width = -size.Width / 4;
    200                                     offset.Height = -size.Height / 4;
    201                                     break;
    202                                 case InfantryStoppingType.UpperRight:
    203                                     offset.Width = size.Width / 4;
    204                                     offset.Height = -size.Height / 4;
    205                                     break;
    206                                 case InfantryStoppingType.LowerLeft:
    207                                     offset.Width = -size.Width / 4;
    208                                     offset.Height = size.Height / 4;
    209                                     break;
    210                                 case InfantryStoppingType.LowerRight:
    211                                     offset.Width = size.Width / 4;
    212                                     offset.Height = size.Height / 4;
    213                                     break;
    214                             }
    215 
    216                             var bounds = new Rectangle(location + offset, size);
    217                             infantryTriggers.Add((infantry.Trigger, bounds));
    218                         }
    219 
    220                         triggers = infantryTriggers.ToArray();
    221                     }
    222 
    223                     if (triggers != null)
    224                     {
    225                         StringFormat stringFormat = new StringFormat
    226                         {
    227                             Alignment = StringAlignment.Center,
    228                             LineAlignment = StringAlignment.Center
    229                         };
    230 
    231                         foreach (var (trigger, bounds) in triggers.Where(x => !x.trigger.Equals("None", StringComparison.OrdinalIgnoreCase)))
    232                         {
    233                             var font = graphics.GetAdjustedFont(trigger, SystemFonts.DefaultFont, bounds.Width, 12 / Globals.TileScale, 24 / Globals.TileScale, true);
    234                             var textBounds = graphics.MeasureString(trigger, font, bounds.Width, stringFormat);
    235 
    236                             var backgroundBounds = new RectangleF(bounds.Location, textBounds);
    237                             backgroundBounds.Offset((bounds.Width - textBounds.Width) / 2.0f, (bounds.Height - textBounds.Height) / 2.0f);
    238                             graphics.FillRectangle(technoTriggerBackgroundBrush, backgroundBounds);
    239                             graphics.DrawRectangle(technoTriggerPen, Rectangle.Round(backgroundBounds));
    240 
    241                             graphics.DrawString(trigger, font, technoTriggerBrush, bounds, stringFormat);
    242                         }
    243                     }
    244                 }
    245             }
    246 
    247             if ((Layers & MapLayerFlag.CellTriggers) != MapLayerFlag.None)
    248             {
    249                 var cellTriggersBackgroundBrush = new SolidBrush(Color.FromArgb(96, Color.Black));
    250                 var cellTriggersBrush = new SolidBrush(Color.FromArgb(128, Color.White));
    251                 var cellTriggerPen = new Pen(Color.White);
    252 
    253                 foreach (var (cell, cellTrigger) in map.CellTriggers)
    254                 {
    255                     var x = cell % map.Metrics.Width;
    256                     var y = cell / map.Metrics.Width;
    257 
    258                     var location = new Point(x * Globals.TileWidth, y * Globals.TileHeight);
    259                     var textBounds = new Rectangle(location, Globals.TileSize);
    260 
    261                     graphics.FillRectangle(cellTriggersBackgroundBrush, textBounds);
    262                     graphics.DrawRectangle(cellTriggerPen, textBounds);
    263 
    264                     StringFormat stringFormat = new StringFormat
    265                     {
    266                         Alignment = StringAlignment.Center,
    267                         LineAlignment = StringAlignment.Center
    268                     };
    269 
    270                     var text = cellTrigger.Trigger;
    271                     var font = graphics.GetAdjustedFont(text, SystemFonts.DefaultFont, textBounds.Width, 24 / Globals.TileScale, 48 / Globals.TileScale, true);
    272                     graphics.DrawString(text.ToString(), font, cellTriggersBrush, textBounds, stringFormat);
    273                 }
    274             }
    275 
    276             if ((Layers & MapLayerFlag.Boundaries) != MapLayerFlag.None)
    277             {
    278                 var boundsPen = new Pen(Color.Cyan, 8.0f);
    279                 var bounds = Rectangle.FromLTRB(
    280                     map.Bounds.Left * Globals.TileWidth,
    281                     map.Bounds.Top * Globals.TileHeight,
    282                     map.Bounds.Right * Globals.TileWidth,
    283                     map.Bounds.Bottom * Globals.TileHeight
    284                 );
    285                 graphics.DrawRectangle(boundsPen, bounds);
    286             }
    287         }
    288 
    289         #region IDisposable Support
    290         private bool disposedValue = false;
    291 
    292         protected virtual void Dispose(bool disposing)
    293         {
    294             if (!disposedValue)
    295             {
    296                 if (disposing)
    297                 {
    298                     navigationWidget.Dispose();
    299 
    300                     mapPanel.PreRender -= MapPanel_PreRender;
    301                     mapPanel.PostRender -= MapPanel_PostRender;
    302 
    303                     map.BasicSection.PropertyChanged -= BasicSection_PropertyChanged;
    304                 }
    305                 disposedValue = true;
    306             }
    307         }
    308 
    309         public void Dispose()
    310         {
    311             Dispose(true);
    312         }
    313         #endregion
    314     }
    315 }