2 using System.Collections.Generic;
4 using System.Runtime.Serialization;
5 using Microsoft.Xna.Framework;
6 using Microsoft.Xna.Framework.Content;
7 using Microsoft.Xna.Framework.Graphics;
8 using System.Reflection;
13 /// This class will be instantiated by the XNA Framework Content
14 /// Pipeline to read cfmap files from binary .xnb format.
16 public class MapReader : ContentTypeReader<Map>
18 #region Public Exceptions
21 /// This exception is thrown during the loading of a map if any
22 /// part of the map file is inconsistent with the expected format
25 public class ParserException : System.ApplicationException
27 public ParserException() {}
29 public ParserException(string message) :
32 public ParserException(string message, System.Exception inner) :
33 base(message, inner) {}
35 protected ParserException(SerializationInfo info, StreamingContext context) :
36 base(info, context) {}
42 #region Protected Methods
44 protected override Map Read(ContentReader input, Map existingInstance)
46 mImpl = new Impl(input);
47 return mImpl.GetMap();
56 /// This private class wraps around ContentReader to make it more
57 /// convenient to use it as an input stream reader.
63 int mExpectedNumberOfLines;
65 public LineReader(ContentReader input)
68 mExpectedNumberOfLines = mInput.ReadInt32();
71 public string ReadLine()
74 return mInput.ReadString();
77 public int LineNumber { get { return mLineNumber; } }
79 public bool End { get { return mLineNumber >= mExpectedNumberOfLines; } }
84 /// This class is the actual implementation. The implementation is wrapped
85 /// in a subclass because the invoker seems to only be able to invoke public
86 /// methods, and this needs to invoke methods that shouldn't be public.
90 public Impl(ContentReader input)
92 mInput = new LineReader(input);
99 return new Map(mMetadata, mGrid, mDefaultTile, mEntities, mPlayerPositions);
103 public void ReadSectionHeaders()
105 mMetadata = new Map.Metadata();
109 string line = mInput.ReadLine();
113 if (!IsLineSignificant(line)) break;
115 string section = Parse.IniSectionHeader(line);
118 if (section == "metadata")
120 line = ReadMetadataSection();
122 else if (section == "maptable")
124 line = ReadMapTableSection();
126 else if (section.Length == 1 && IsValidEntityIdentifier(section[0]))
128 line = ReadEntitySection(section[0]);
132 throw new ParserException("Unexpected section on line " + mInput.LineNumber + ": " + section);
137 throw new ParserException("Unexpected text on line " + mInput.LineNumber + ": " + line);
143 string ReadMetadataSection()
147 string line = mInput.ReadLine();
148 if (!IsLineSignificant(line)) continue;
150 string[] pair = Parse.KeyValuePair(line);
155 string methodName = "set_" + pair[0].ToLowerInvariant();
156 object[] args = new object[1];
158 GetType().InvokeMember(methodName, BindingFlags.InvokeMethod, null, this, args);
160 #pragma warning disable 0168
161 catch (System.MissingMethodException ex)
162 #pragma warning restore 0168
164 throw new ParserException("Unexpected key on line " + mInput.LineNumber + ": " + pair[0]);
176 string ReadMapTableSection()
178 if (mMetadata == null || mMetadata.GridWidth == 0 || mMetadata.GridHeight == 0)
180 throw new ParserException("Unexpected section on line " + mInput.LineNumber +
181 ": You must define the map dimensions before this section.");
184 mGrid = new char[mMetadata.GridWidth, mMetadata.GridHeight];
187 for (y = 0; y < mMetadata.GridHeight && !mInput.End; y++)
189 string line = mInput.ReadLine();
191 if (line.Length < mMetadata.GridWidth)
193 throw new ParserException("Unexpected EOL on line " + mInput.LineNumber +
194 ": The number of characters should match the width dimension (" + mMetadata.GridWidth + ").");
197 for (int x = 0; x < mMetadata.GridWidth; x++)
199 mGrid[x, y] = line[x];
203 if (y < mMetadata.GridHeight)
205 throw new ParserException("Unexpected EOF on line " + mInput.LineNumber +
206 ": The number of lines in this section should match the height dimension (" + mMetadata.GridHeight + ").");
212 string ReadEntitySection(char identifier)
214 Dictionary<string, string> pairs = new Dictionary<string, string>();
215 mEntitySections[identifier] = pairs;
219 string line = mInput.ReadLine();
221 string[] pair = Parse.KeyValuePair(line);
224 pairs[pair[0]] = pair[1];
238 if (mMetadata == null || mGrid == null)
240 throw new ParserException("Missing a required section. Make sure the metadata and grid are there.");
243 mEntities = new List<Map.RawEntity>();
244 mPlayerPositions = new Point[mMetadata.NumPlayers.Max() + 1];
246 // create entities defined completely
247 foreach (char identifier in mEntitySections.Keys)
249 Dictionary<string, string> pairs = mEntitySections[identifier];
250 if (pairs.ContainsKey("create"))
252 string[] list = Parse.List(pairs["create"]);
253 foreach (string positionString in list)
255 Point? position = Parse.Coordinates(positionString);
256 if (position != null)
258 Map.RawEntity createEntity = new Map.RawEntity();
259 createEntity.Id = identifier;
260 createEntity.Position = position.Value;
261 createEntity.Attributes = pairs;
262 mEntities.Add(createEntity);
266 throw new ParserException("Unexpected value of key `create' defined for entity " + identifier + ".");
269 pairs.Remove("create");
273 // create entities with positions defined on the grid
274 // and get player starting positions
275 for (int x = 0; x < mMetadata.GridWidth; x++)
277 for (int y = 0; y < mMetadata.GridHeight; y++)
279 char identifier = mGrid[x, y];
280 if (IsValidEntityIdentifier(identifier))
282 if (mEntitySections.ContainsKey(identifier))
284 Map.RawEntity createEntity = new Map.RawEntity();
285 createEntity.Id = identifier;
286 createEntity.Position = new Point(x, y);
287 createEntity.Attributes = mEntitySections[identifier];
288 mEntities.Add(createEntity);
292 throw new ParserException("Unexpected entity (" + identifier +
293 ") placed on the grid at [" + x + "," + y + "] but not defined.");
295 mGrid[x, y] = mDefaultTile;
297 else if ('1' <= identifier && identifier <= '9')
299 int playerNum = identifier - 48;
300 if (playerNum < mPlayerPositions.Count())
302 mPlayerPositions[playerNum] = new Point(x, y);
304 mGrid[x, y] = mDefaultTile;
309 // check if all needed player positions are defined
310 for (int i = 1; i < mPlayerPositions.Count(); i++)
312 if (mPlayerPositions[i] == default(Point))
314 throw new ParserException("Not enough player positions were defined on the grid; " +
315 "you are missing a spot for player " + i + ".");
321 bool IsLineSignificant(string line)
323 if (line.Trim().Length == 0 || Parse.IniComment(line) != null) return false;
327 bool IsValidEntityIdentifier(char id)
329 if (('a' <= id && id <= 'z') || ('A' <= id && id <= 'Z')) return true;
334 public void set_author(string atom)
336 string value = Parse.String(atom);
337 if (value != null) mMetadata.Author = value;
338 else throw new ParserException("Unexpected value on line " + mInput.LineNumber + ": " + atom);
341 public void set_levelname(string atom)
343 string value = Parse.String(atom);
344 if (value != null) mMetadata.Name = value;
345 else throw new ParserException("Unexpected value on line " + mInput.LineNumber + ": " + atom);
348 public void set_type(string atom)
350 Map.Mode value = Parse.Constant<Map.Mode>(atom);
351 if (value != default(Map.Mode)) mMetadata.Type = value;
352 else throw new ParserException("Unexpected type on line " + mInput.LineNumber + ": " + atom);
355 public void set_dimensions(string atom)
357 Point? dimensions = Parse.Coordinates(atom);
358 if (dimensions != null)
360 mMetadata.GridWidth = dimensions.Value.X;
361 mMetadata.GridHeight = dimensions.Value.Y;
362 if (mMetadata.GridWidth <= 0 || mMetadata.GridHeight <= 0)
364 throw new ParserException("Invalid dimensions on line " + mInput.LineNumber + ": " + atom);
369 throw new ParserException("Unexpected value on line " + mInput.LineNumber + ": " + atom);
373 public void set_tileset(string atom)
375 string value = Parse.String(atom);
376 if (value != null) mMetadata.Tileset = value;
377 else throw new ParserException("Unexpected tileset on line " + mInput.LineNumber + ": " + atom);
380 public void set_defaulttile(string atom)
382 char? value = Parse.Char(atom);
383 if (value != null) mDefaultTile = value.Value;
384 else throw new ParserException("Unexpected tile value on line " + mInput.LineNumber + ": " + atom);
387 public void set_numplayers(string atom)
389 string[] list = Parse.List(atom);
392 foreach (string item in list)
394 int[] range = Parse.Range(item);
397 for (int i = range[0]; i <= range[1]; i++)
399 mMetadata.NumPlayers.Add(i);
403 int? integer = Parse.Integer(item);
406 mMetadata.NumPlayers.Add(integer.Value);
410 throw new ParserException("Unexpected atom on line " + mInput.LineNumber + ": " + item);
412 if (mMetadata.NumPlayers.Count == 0)
414 throw new ParserException("No numbers given on line " + mInput.LineNumber + ": " + atom);
419 throw new ParserException("Unexpected value on line " + mInput.LineNumber + ": " + atom);
424 Map.Metadata mMetadata;
426 List<Map.RawEntity> mEntities;
427 Point[] mPlayerPositions;
428 char mDefaultTile = ' ';
430 Dictionary<char, Dictionary<string, string>> mEntitySections = new Dictionary<char, Dictionary<string, string>>();
437 #region Private Variables