PageRenderTime 489ms CodeModel.GetById 100ms app.highlight 288ms RepoModel.GetById 45ms app.codeStats 1ms

/WorldView/WorldMapper.cs

#
C# | 573 lines | 385 code | 100 blank | 88 comment | 100 complexity | 3d0cfae67fff74b0ab7cdd375074fe81 MD5 | raw file
  1namespace MoreTerra
  2{
  3    using System;
  4    using System.Collections.Generic;
  5    using System.Drawing;
  6    using System.Drawing.Imaging;
  7	using System.IO;
  8    using System.Windows.Forms;
  9	using MoreTerra.Utilities;
 10	using MoreTerra.Structures;
 11	using System.ComponentModel;
 12	using MoreTerra.Structures.TerraInfo;
 13
 14    public class WorldMapper
 15    {
 16        private List<Chest> chests;
 17        private Dictionary<MarkerType, List<MarkerLoc>> tileMarkersToAdd;
 18        private Int16[,] tiles;
 19
 20		private World world;
 21
 22		int maxX, maxY;
 23
 24        public int progress = 0;
 25		
 26        public WorldMapper()
 27        {
 28            chests = new List<Chest>();
 29        }
 30
 31        public void Initialize()
 32        {
 33			TileData td;
 34			// Now we set DrawMarker for each of the markers we can potentially draw.
 35			// This makes for a much faster lookup to see if we need to draw an marker
 36			// rather than mass calling the DrawMarker function.
 37			for (Int32 i = 0; i < TileProperties.tileTypeDefs.Length; i++)
 38			{
 39				td = TileProperties.tileTypeDefs[i];
 40
 41				if (td.MarkerType != MarkerType.Unknown)
 42				{
 43					td.DrawMarker = SettingsManager.Instance.DrawMarker((Int16) i);
 44				}
 45			}
 46        }
 47
 48        public void OpenWorld()
 49        {
 50			world = new World();
 51        }
 52
 53        public void ProcessWorld(String worldPath, BackgroundWorker bw)
 54        {
 55
 56			tiles = world.ReadAndProcessWorld(worldPath, bw);
 57			if (tiles == null)
 58				return;
 59	
 60			progress = 45;
 61
 62			maxX = world.Header.MaxTiles.X;
 63			maxY = world.Header.MaxTiles.Y;
 64
 65            // Reset Marker List
 66            tileMarkersToAdd = new Dictionary<MarkerType, List<MarkerLoc>>();
 67
 68			// I got horribly tired of having to constantly to use ContainsKey searches just to be
 69			// positive we don't try to access a list that is uninitialized so I just started
 70			// them all up here.  The drawing code will quickly skip empty ones without drawing
 71			// anyways.  
 72			Array m = Enum.GetValues(typeof(MarkerType));
 73			foreach (MarkerType mt in m)
 74			{
 75				tileMarkersToAdd.Add(mt, new List<MarkerLoc>());
 76			}
 77
 78        
 79
 80			if (bw != null)
 81				bw.ReportProgress(45, "Processing Chests");
 82
 83            List<String> itemFilters = SettingsManager.Instance.FilterItemStates;
 84
 85			// Read the Chests
 86			this.chests = world.Chests;
 87
 88			foreach (Chest chest in this.chests)
 89			{
 90				progress = (int)(((float)chest.ChestId / (float)Global.ChestMaxNumber) * 5f + 45f);
 91
 92				// See if we are bothering to draw chests at all.
 93				if (SettingsManager.Instance.DrawMarker(chest.Type) == true)
 94				{
 95					// Find out if the chest is relevant to our interests based on what is in it.
 96					foreach (Item item in chest.Items)
 97					{
 98						// If we're not filtering or if we want it
 99						if (!SettingsManager.Instance.FilterChests || itemFilters.Contains(item.Name))
100						{
101							// It passed all checks, add it to the list.
102							if (chest.Type == ChestType.Chest)
103								tileMarkersToAdd[MarkerType.Chest].Add(new MarkerLoc(chest.Coordinates, 1));
104							else
105								tileMarkersToAdd[MarkerType.Chest + (Int32) chest.Type].Add(
106									new MarkerLoc(chest.Coordinates, 1));
107
108							break;
109						}
110					}
111				}
112            }
113
114			if (bw != null)
115				bw.ReportProgress(50, "Processing Signs");
116
117			// Pull all of the Signs out of the file.
118			foreach (Sign newSign in world.Signs)
119			{
120				if (newSign.Active)
121				{
122					if (SettingsManager.Instance.DrawMarker(MarkerType.Sign))
123					{
124						tileMarkersToAdd[MarkerType.Sign].Add(new MarkerLoc(newSign.Position, 1));
125					}
126				}
127			}
128
129			
130			if (bw != null)
131				bw.ReportProgress(50, "Processing Npcs");
132
133
134			foreach (NPC newNPC in world.Npcs)
135			{
136				if (newNPC == null)
137					continue;
138
139				if (newNPC.Type == NPCType.Unknown)
140					continue;
141
142				MarkerType tt = MarkerType.ArmsDealer + (Int32)newNPC.Type;
143
144				if (SettingsManager.Instance.DrawMarker(newNPC.Type) == true)
145				{
146					// I didn't think we ever needed more than one of each NPC.  Then I remembered that
147					// if conditions are right two nurses and three merchants can spawn.
148					tileMarkersToAdd[tt].Add(new MarkerLoc(
149						new Point((Int32) (newNPC.Position.X/16), (Int32) (newNPC.Position.Y/16)), 1));
150				}
151
152				
153			}
154
155			progress = 50;
156			
157		}
158	
159		// Called during worker_GenerateMap.  This will only get used if
160		// we are only doing a LoadInformation button press call.
161        public void ReadChests(String worldPath, BackgroundWorker bw)
162        {
163            progress = 0;
164
165			chests = world.GetChests(worldPath, bw);
166        }
167
168		public Bitmap CreatePreviewPNG(string outputPngPath, BackgroundWorker bw)
169        {
170            Boolean useOfficialColors = SettingsManager.Instance.OfficialColors;
171			Int32 CropAmount;
172			int row, col;
173			Bitmap bitmap;
174
175			switch(SettingsManager.Instance.CropImageUsing)
176			{
177				case 1:
178					CropAmount = Global.OldLightingCrop;
179					break;
180				case 2:
181					CropAmount = Global.NewLightingCrop;
182					break;
183				default:
184					CropAmount = 0;
185					break;
186			}
187
188			if (CropAmount > 0)
189			{
190				bitmap = new Bitmap(maxX - (2 * CropAmount) - 1, maxY - (2 * CropAmount) - 1
191					, PixelFormat.Format24bppRgb);
192			}
193			else
194			{
195				bitmap = new Bitmap(maxX, maxY, PixelFormat.Format24bppRgb);
196			}
197            Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
198            Graphics graphicsHandle = Graphics.FromImage((Image)bitmap);
199
200            //graphicsHandle.FillRectangle(new SolidBrush(Constants.Colors.SKY), 0, 0, bitmap.Width, bitmap.Height);
201
202            System.Drawing.Imaging.BitmapData bmpData = bitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, bitmap.PixelFormat);
203
204            IntPtr ptr = bmpData.Scan0;
205            int bytes = Math.Abs(bmpData.Stride) * bitmap.Height;
206            byte[] rgbValues = new byte[bytes];
207            const int byteOffset = 3;
208
209
210			TileData tileInfo;
211			MarkerType markerType;
212            Int16 tileType;
213			Color color;
214
215			if (bw != null)
216				bw.ReportProgress(50, "Drawing Map");
217
218			if (CropAmount > 0)
219			{
220				for (row = 0; row < CropAmount; row++)
221				{
222					for (col = 0; col < maxX; col++)
223					{
224						tiles[col, row] = TileProperties.Cropped;
225					}
226				}
227				for (row = maxY - CropAmount - 1; row < maxY; row++)
228				{
229					for (col = 0; col < maxX; col++)
230					{
231						tiles[col, row] = TileProperties.Cropped;
232					}
233				}
234				for (row = 0; row < maxY; row++)
235				{
236					for (col = 0; col < CropAmount; col++)
237					{
238						tiles[col, row] = TileProperties.Cropped;
239					}
240				}
241				for (row = 0; row < maxY; row++)
242				{
243					for (col = maxX - CropAmount - 1; col < maxX; col++)
244					{
245						tiles[col, row] = TileProperties.Cropped;
246					}
247				}
248			}
249
250            for (row = 0; row < maxY; row++)
251            {
252                progress = (int)(((float)row / (float)maxY) * 40f + 50f);
253
254				int index = (bmpData.Stride * (row - CropAmount)) - (1 * byteOffset);    //first increment will be 0;
255				for (col = 0; col < maxX; col++)
256                {
257					if (tiles[col, row] == TileProperties.Cropped)
258						continue;
259
260					index += byteOffset;    //increase here to avoid adding increments to each continue
261                    tileType = tiles[col, row];
262                
263                    // Skip Walls
264                    if (!SettingsManager.Instance.DrawWalls && tileType > TileProperties.BackgroundOffset)
265						tileType = TileProperties.BackgroundOffset;
266
267					if (tileType < TileProperties.Processed)
268					{
269						tileInfo = TileProperties.tileTypeDefs[tileType];
270
271						if (tileInfo.DrawMarker)
272						{
273							// If we have already processed this then skip it.
274							Int32 tileCount = 1;
275							Int32 foundCol = col;
276							Int32 foundRow = row;
277								
278							#region AdvancedMarkerComment
279							/*
280								* First off, we know from the way the level is processed we never have to
281								* check up as if something higher than the start existed we'd have hit it
282								* while processing down anyways.
283								* 
284								* We could parse around and find only stuff that is directly connected to
285								* each other and have a "perfect" look but that would take more processing
286								* than I'm willing to do.  Instead we'll keep a bounding box around what
287								* we've found and use that in the end.
288								* 
289								* To keep things from getting out of hand from player made clumps of things
290								* there is a maximum size the box can be.
291								* 
292								* ----------
293								* Pseudocode
294								* ----------
295								* First we check to see if we expanded since last run.
296								* Then we check all rows but the bottom row for further expansion.
297								* One pass one we'll skip the top row as it's also the bottom row.
298								* 
299								* Next we scan the side edges of the bottom row to try and expand.
300								* Then we scan the row below the bottom for vertical expansion.
301								* 
302								* If there was vertical expansion we now have a new bottom row and
303								* so we loop through scanning below the bottom row again.
304								* 
305								* Once we run out of vertical expansion we'll loop back (if we expanded
306								* at all) and rescan the edges to see if by expanding we opened up
307								* something new in those newly opened sides.
308								* 
309								* Once we do one full pass without any new expansion we have our final
310								* bounding box so we scan straight through from one end to the other
311								* and call everything that matches that we find in it our marker block.
312								*/
313							#endregion
314
315							Int32 boundsMax = 32;
316							Rectangle bounds = new Rectangle(col, row, 1, 1);
317
318							Int32 screenMaxWidth = world.Header.MaxTiles.X;
319							Int32 screenMaxHeight = world.Header.MaxTiles.Y;
320							Boolean expandedHoriz = false;
321							Boolean expandedVert = false;
322							Boolean doneProcessing = true;
323							Int32 i, j;
324
325
326							do
327							{
328								doneProcessing = true;
329
330								for (j = bounds.Y; j < row + boundsMax; j++)
331								{
332									if (bounds.Bottom >= screenMaxHeight || bounds.Height >= boundsMax)
333										break;
334
335									expandedVert = false;
336
337									// We only scan the sides if we either changed the width since last
338									// loop or we are on the bottom row.
339									if (expandedHoriz == true || j == (bounds.Bottom - 1))
340									{
341										expandedHoriz = false;
342
343										#region ExpandLeft
344
345										if (tiles[bounds.X, j] == tileType)
346										{
347											for (i = bounds.X - 1; i > (col - boundsMax); i--)
348											{
349												if (i <= 0)
350													break;
351
352												if (tiles[i, j] == tileType)
353												{
354													bounds.X = i;
355													bounds.Width++;
356													expandedHoriz = true;
357													doneProcessing = false;
358												}
359												else
360												{
361													break;
362												}
363
364											}
365										}
366										#endregion
367
368										#region ExpandRight
369										if (tiles[bounds.Right - 1, j] == tileType)
370										{
371											for (i = bounds.Right; i < (bounds.X + boundsMax); i++)
372											{
373												if (i >= screenMaxWidth)
374													break;
375
376												if (tiles[i, j] == tileType)
377												{
378													bounds.Width++;
379													expandedHoriz = true;
380													doneProcessing = false;
381
382													if (bounds.Width == boundsMax)
383														break;
384												}
385												else
386												{
387													break;
388												}
389											}
390										}
391										#endregion
392									}
393
394									if (j == (bounds.Bottom - 1))
395									{
396										#region ExpandDown
397
398										if (j + 1 < screenMaxHeight && bounds.Height < boundsMax)
399										{
400											for (i = bounds.X; i < (bounds.Right); i++)
401											{
402												if (tiles[i, j] == tileType)
403												{
404													if (tiles[i, j + 1] == tileType)
405													{
406														if (j + 1 >= bounds.Bottom)
407														{
408															bounds.Height++;
409															expandedVert = true;
410															doneProcessing = false;
411															break;
412														}
413													}
414												}
415											}
416
417										}
418										#endregion
419									}
420									if (expandedVert == false)
421										break;
422								}
423							} while (doneProcessing == false);
424
425							Int32 tilePos;
426							tileCount = 0;
427
428                            if (useOfficialColors == true)
429                                color = tileInfo.OfficialColor;
430                            else
431							    color = tileInfo.Color;
432
433							for (j = bounds.Y; j < bounds.Bottom; j++)
434								for (i = bounds.X; i < bounds.Right; i++)
435									if (tiles[i, j] == tileType)
436									{
437										tileCount++;
438
439										tiles[i, j] = TileProperties.Processed;
440										tilePos = (j - CropAmount) * bmpData.Stride + 
441												  (i - CropAmount) * byteOffset;
442
443										rgbValues[tilePos] = color.B;
444										rgbValues[tilePos + 1] = color.G;
445										rgbValues[tilePos + 2] = color.R;
446                                        
447									}
448
449							foundCol = (int)(bounds.X + (bounds.Width / 2));
450							foundRow = (int)(bounds.Y + (bounds.Height / 2));
451
452							markerType = TileProperties.tileTypeDefs[tileType].MarkerType;
453
454							tileMarkersToAdd[markerType].Add(new MarkerLoc(new Point(foundCol, foundRow), tileCount));
455						}
456					}
457
458					// This used to not draw at all if you chose to put a marker down for that type, which makes
459					// sense as the marker did cover it.  However this also caused a bug where things you had not
460					// chosen to put a marker didn't draw their pixel color in.  There was a different fix for
461					// this but combining the fact that skipping that draw saved very little time and that
462					// with the new "One marker per set of items" instead of "One marker per item" mechanic
463					// means that you might see some of the color hanging out the edge.
464					if (tileType != TileProperties.Processed)
465					{
466                        if (useOfficialColors)
467                            color = TileProperties.tileTypeDefs[tileType].OfficialColor;
468						else 
469                            color = TileProperties.tileTypeDefs[tileType].Color;
470
471						rgbValues[index] = color.B;
472						rgbValues[index + 1] = color.G;
473						rgbValues[index + 2] = color.R;
474					}
475
476                }
477            }
478
479            System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes);
480            bitmap.UnlockBits(bmpData);
481
482
483            // Add Spawn
484			if (SettingsManager.Instance.DrawMarker(MarkerType.Spawn) == true)
485			{
486				tileMarkersToAdd[MarkerType.Spawn].Add(new MarkerLoc(new Point(
487					world.Header.SpawnPoint.X, world.Header.SpawnPoint.Y), 1));
488			}
489
490			if (bw != null)
491				bw.ReportProgress(90, "Drawing markers");
492			Int32 count = 0;
493            // Draw Markers
494            foreach (KeyValuePair<MarkerType, List<MarkerLoc>> kv in tileMarkersToAdd)
495            {
496				if (kv.Key == MarkerType.Unknown)
497					continue;
498
499				progress = (Int32)(((Double)count / tileMarkersToAdd.Count) * 10f + 90f);
500                Bitmap markerBitmap = ResourceManager.Instance.GetMarker(kv.Key);
501
502                foreach (MarkerLoc sl in kv.Value)
503                {
504                    int x = Math.Max((int)sl.pv.X - (markerBitmap.Width / 2) - CropAmount, 0);
505                    int y = Math.Max((int)sl.pv.Y - (markerBitmap.Height / 2) - CropAmount, 0);
506					
507					if (x > maxX || y > maxY) continue;
508
509                    graphicsHandle.DrawImage(markerBitmap, x, y);
510                }
511            }
512			if (bw != null)
513				bw.ReportProgress(99, "Saving image");
514            
515            bitmap.Save(outputPngPath, ImageFormat.Png);
516            progress = 100;
517            return bitmap;
518        }
519
520		public void Cleanup()
521		{
522			// We will set everything to null, except for Chests as we still use them
523			// to do our Chest list sorting.
524			this.tileMarkersToAdd = null;
525			this.tiles = null;
526			this.world = null;
527
528
529		}
530
531		// This code is just to runs tests with.
532
533/*		public void ScanAndCompare(String worldPath, BackgroundWorker bw)
534		{
535			World.TileImportance[] imp;
536
537			world = new World();
538			imp = world.ScanWorld(worldPath, bw);
539
540			for (Int32 j = 0; j < imp.GetLength(0); j++)
541			{
542				for (Int32 i = 0; i < 256; i++)
543				{
544					if (imp[j].isKnown((Byte)i))
545					{
546						if (imp[j].isImportant((Byte)i) != tileTypeDefs[i].IsImportant)
547							i = i;
548					}
549
550				}
551			}
552		}*/
553
554		#region GetSet Functions
555		public List<Chest> Chests
556        {
557            get
558            {
559                return this.chests;
560            }
561        }
562
563		public World World
564		{
565			get
566			{
567				return world;
568			}
569		}
570
571		#endregion
572    }
573}