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 }