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 Protected Methods
20 protected override Map Read(ContentReader input, Map existingInstance)
22 mImpl = new Impl(input);
23 return mImpl.GetMap();
32 /// This private class wraps around ContentReader to make it more
33 /// convenient to use it as an input stream reader.
39 int mExpectedNumberOfLines;
41 public LineReader(ContentReader input)
44 mExpectedNumberOfLines = mInput.ReadInt32();
47 public string ReadLine()
50 return mInput.ReadString();
53 public int LineNumber { get { return mLineNumber; } }
55 public bool End { get { return mLineNumber >= mExpectedNumberOfLines; } }
60 /// This class is the actual implementation. The implementation is wrapped
61 /// in a subclass because the invoker seems to only be able to invoke public
62 /// methods, and this needs to invoke methods that shouldn't be public.
66 public Impl(ContentReader input)
68 mInput = new LineReader(input);
75 return new Map(mMetadata, mGrid, mDefaultTile, mEntities, mPlayerPositions);
79 public void ReadSectionHeaders()
81 mMetadata = new Map.Metadata();
85 string line = mInput.ReadLine();
89 if (!IsLineSignificant(line)) break;
91 string section = Parse.IniSectionHeader(line);
94 if (section == "metadata")
96 line = ReadMetadataSection();
98 else if (section == "maptable")
100 line = ReadMapTableSection();
102 else if (section.Length == 1 && IsValidEntityIdentifier(section[0]))
104 line = ReadEntitySection(section[0]);
108 throw new Exception("Unexpected section on line " + mInput.LineNumber + ": " + section);
113 throw new Exception("Unexpected text on line " + mInput.LineNumber + ": " + line);
119 string ReadMetadataSection()
123 string line = mInput.ReadLine();
124 if (!IsLineSignificant(line)) continue;
126 string[] pair = Parse.KeyValuePair(line);
131 string methodName = "set_" + pair[0].ToLowerInvariant();
132 object[] args = new object[1];
134 GetType().InvokeMember(methodName, BindingFlags.InvokeMethod, null, this, args);
136 #pragma warning disable 0168
137 catch (System.MissingMethodException ex)
138 #pragma warning restore 0168
140 throw new Exception("Unexpected key on line " + mInput.LineNumber + ": " + pair[0]);
152 string ReadMapTableSection()
154 if (mMetadata == null || mMetadata.GridWidth == 0 || mMetadata.GridHeight == 0)
156 throw new Exception("Unexpected section on line " + mInput.LineNumber +
157 ": You must define the map dimensions before this section.");
160 mGrid = new char[mMetadata.GridWidth, mMetadata.GridHeight];
163 for (y = 0; y < mMetadata.GridHeight && !mInput.End; y++)
165 string line = mInput.ReadLine();
167 if (line.Length < mMetadata.GridWidth)
169 throw new Exception("Unexpected EOL on line " + mInput.LineNumber +
170 ": The number of characters should match the width dimension (" + mMetadata.GridWidth + ").");
173 for (int x = 0; x < mMetadata.GridWidth; x++)
175 mGrid[x, y] = line[x];
179 if (y < mMetadata.GridHeight)
181 throw new Exception("Unexpected EOF on line " + mInput.LineNumber +
182 ": The number of lines in this section should match the height dimension (" + mMetadata.GridHeight + ").");
188 string ReadEntitySection(char identifier)
190 Dictionary<string, string> pairs = new Dictionary<string, string>();
191 mEntitySections[identifier] = pairs;
195 string line = mInput.ReadLine();
197 string[] pair = Parse.KeyValuePair(line);
200 pairs[pair[0]] = pair[1];
214 if (mMetadata == null || mGrid == null)
216 throw new Exception("Missing a required section. Make sure the metadata and grid are there.");
219 mEntities = new List<Map.RawEntity>();
220 mPlayerPositions = new Point[mMetadata.NumPlayers.Max() + 1];
222 // create entities defined completely
223 foreach (char identifier in mEntitySections.Keys)
225 Dictionary<string, string> pairs = mEntitySections[identifier];
226 if (pairs.ContainsKey("create"))
228 string[] list = Parse.List(pairs["create"]);
229 foreach (string positionString in list)
231 Point? position = Parse.Coordinates(positionString);
232 if (position != null)
234 Map.RawEntity createEntity = new Map.RawEntity();
235 createEntity.Id = identifier;
236 createEntity.Position = position.Value;
237 createEntity.Attributes = pairs;
238 mEntities.Add(createEntity);
242 throw new Exception("Unexpected value of key `create' defined for entity " + identifier + ".");
245 pairs.Remove("create");
249 // create entities with positions defined on the grid
250 // and get player starting positions
251 for (int x = 0; x < mMetadata.GridWidth; x++)
253 for (int y = 0; y < mMetadata.GridHeight; y++)
255 char identifier = mGrid[x, y];
256 if (IsValidEntityIdentifier(identifier))
258 if (mEntitySections.ContainsKey(identifier))
260 Map.RawEntity createEntity = new Map.RawEntity();
261 createEntity.Id = identifier;
262 createEntity.Position = new Point(x, y);
263 createEntity.Attributes = mEntitySections[identifier];
264 mEntities.Add(createEntity);
268 throw new Exception("Unexpected entity (" + identifier +
269 ") placed on the grid at [" + x + "," + y + "] but not defined.");
271 mGrid[x, y] = mDefaultTile;
273 else if ('1' <= identifier && identifier <= '9')
275 int playerNum = identifier - 48;
276 if (playerNum < mPlayerPositions.Count())
278 mPlayerPositions[playerNum] = new Point(x, y);
280 mGrid[x, y] = mDefaultTile;
285 // check if all needed player positions are defined
286 for (int i = 1; i < mPlayerPositions.Count(); i++)
288 if (mPlayerPositions[i] == default(Point))
290 throw new Exception("Not enough player positions were defined on the grid; " +
291 "you are missing a spot for player " + i + ".");
297 bool IsLineSignificant(string line)
299 if (line.Trim().Length == 0 || Parse.IniComment(line) != null) return false;
303 bool IsValidEntityIdentifier(char id)
305 if (('a' <= id && id <= 'z') || ('A' <= id && id <= 'Z')) return true;
310 public void set_author(string atom)
312 string value = Parse.String(atom);
313 if (value != null) mMetadata.Author = value;
314 else throw new Exception("Unexpected value on line " + mInput.LineNumber + ": " + atom);
317 public void set_levelname(string atom)
319 string value = Parse.String(atom);
320 if (value != null) mMetadata.Name = value;
321 else throw new Exception("Unexpected value on line " + mInput.LineNumber + ": " + atom);
324 public void set_type(string atom)
326 Map.Mode value = Parse.Constant<Map.Mode>(atom);
327 if (value != default(Map.Mode)) mMetadata.Type = value;
328 else throw new Exception("Unexpected type on line " + mInput.LineNumber + ": " + atom);
331 public void set_dimensions(string atom)
333 Point? dimensions = Parse.Coordinates(atom);
334 if (dimensions != null)
336 mMetadata.GridWidth = dimensions.Value.X;
337 mMetadata.GridHeight = dimensions.Value.Y;
338 if (mMetadata.GridWidth <= 0 || mMetadata.GridHeight <= 0)
340 throw new Exception("Invalid dimensions on line " + mInput.LineNumber + ": " + atom);
345 throw new Exception("Unexpected value on line " + mInput.LineNumber + ": " + atom);
349 public void set_tileset(string atom)
351 string value = Parse.String(atom);
352 if (value != null) mMetadata.Tileset = value;
353 else throw new Exception("Unexpected tileset on line " + mInput.LineNumber + ": " + atom);
356 public void set_defaulttile(string atom)
358 char? value = Parse.Char(atom);
359 if (value != null) mDefaultTile = value.Value;
360 else throw new Exception("Unexpected tile value on line " + mInput.LineNumber + ": " + atom);
363 public void set_numplayers(string atom)
365 string[] list = Parse.List(atom);
368 foreach (string item in list)
370 int[] range = Parse.Range(item);
373 for (int i = range[0]; i <= range[1]; i++)
375 mMetadata.NumPlayers.Add(i);
379 int? integer = Parse.Integer(item);
382 mMetadata.NumPlayers.Add(integer.Value);
386 throw new Exception("Unexpected atom on line " + mInput.LineNumber + ": " + item);
388 if (mMetadata.NumPlayers.Count == 0)
390 throw new Exception("No numbers given on line " + mInput.LineNumber + ": " + atom);
395 throw new Exception("Unexpected value on line " + mInput.LineNumber + ": " + atom);
400 Map.Metadata mMetadata;
402 List<Map.RawEntity> mEntities;
403 Point[] mPlayerPositions;
404 char mDefaultTile = ' ';
406 Dictionary<char, Dictionary<string, string>> mEntitySections = new Dictionary<char, Dictionary<string, string>>();
413 #region Private Variables