using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; namespace CarFire { /// /// This class will be instantiated by the XNA Framework Content /// Pipeline to read cfmap files from binary .xnb format. /// public class MapReader : ContentTypeReader { #region Public Exceptions /// /// This exception is thrown during the loading of a map if any /// part of the map file is inconsistent with the expected format /// and order. /// public class ParserException : System.ApplicationException { public ParserException() {} public ParserException(string message) : base(message) {} public ParserException(string message, System.Exception inner) : base(message, inner) {} protected ParserException(SerializationInfo info, StreamingContext context) : base(info, context) {} } #endregion #region Protected Methods protected override Map Read(ContentReader input, Map existingInstance) { mInput = new LineReader(input); ReadSectionHeaders(); return new Map(mMetadata, mGrid, mEntities); } #endregion #region Private Methods void ReadSectionHeaders() { mMetadata = new Map.Metadata(); while (!mInput.End) { string line = mInput.ReadLine(); while (line != null) { if (!IsLineSignificant(line)) break; string section = Parse.IniSectionHeader(line); if (section != null) { if (section == "metadata") { line = ReadMetadataSection(); } else if (section == "maptable") { line = ReadMapTableSection(); } else if (section.Length == 1) { line = ReadEntitySection(section[0]); } else { throw new ParserException("Unexpected section on line " + mInput.LineNumber + ": " + section); } } else { throw new ParserException("Unexpected text on line " + mInput.LineNumber + ": " + line); } } } } string ReadMetadataSection() { while (!mInput.End) { string line = mInput.ReadLine(); if (!IsLineSignificant(line)) continue; string[] pair = Parse.KeyValuePair(line); if (pair != null) { if (pair[0] == "type") { Map.Type type = Parse.Constant(pair[1]); if (type != default(Map.Type)) { mMetadata.Type = type; } else { throw new ParserException("Unexpected type on line " + mInput.LineNumber + ": " + pair[1]); } } else if (pair[0] == "dimensions") { Point? dimensions = Parse.Coordinates(pair[1]); if (dimensions != null) { mMetadata.GridWidth = dimensions.Value.X; mMetadata.GridHeight = dimensions.Value.Y; if (mMetadata.GridWidth <= 0 || mMetadata.GridHeight <= 0) { throw new ParserException("Invalid dimensions on line " + mInput.LineNumber + ": " + pair[1]); } } else { throw new ParserException("Unexpected value on line " + mInput.LineNumber + ": " + pair[1]); } } else if (pair[0] == "tileset") { string tileset = Parse.String(pair[1]); if (tileset != null) { mMetadata.Tileset = tileset; } else { throw new ParserException("Unexpected tileset on line " + mInput.LineNumber + ": " + pair[1]); } } else if (pair[0] == "numplayers") { string[] list = Parse.List(pair[1]); if (list != null) { foreach (string atom in list) { int[] range = Parse.Range(atom); if (range != null) { for (int i = range[0]; i <= range[1]; i++) { mMetadata.NumPlayers.Add(i); } continue; } int? integer = Parse.Integer(atom); if (integer != null) { mMetadata.NumPlayers.Add(integer.Value); continue; } throw new ParserException("Unexpected atom on line " + mInput.LineNumber + ": " + atom); } if (mMetadata.NumPlayers.Count == 0) { throw new ParserException("No numbers given on line " + mInput.LineNumber + ": " + pair[1]); } } else { throw new ParserException("Unexpected value on line " + mInput.LineNumber + ": " + pair[1]); } } else if (pair[0] == "author") { string author = Parse.String(pair[1]); if (author != null) { mMetadata.Author = author; } else { throw new ParserException("Unexpected value on line " + mInput.LineNumber + ": " + pair[1]); } } else if (pair[0] == "levelname") { string level = Parse.String(pair[1]); if (level != null) { mMetadata.Name = level; } else { throw new ParserException("Unexpected value on line " + mInput.LineNumber + ": " + pair[1]); } } else { throw new ParserException("Unexpected key on line " + mInput.LineNumber + ": " + pair[0]); } } else { return line; } } return null; } string ReadMapTableSection() { if (mMetadata == null || mMetadata.GridWidth == 0 || mMetadata.GridHeight == 0) { throw new ParserException("Unexpected section on line " + mInput.LineNumber + ": You must define the map dimensions before this section."); } mGrid = new char[mMetadata.GridWidth, mMetadata.GridHeight]; int y; for (y = 0; y < mMetadata.GridHeight && !mInput.End; y++) { string line = mInput.ReadLine(); if (line.Length < mMetadata.GridWidth) { throw new ParserException("Unexpected EOL on line " + mInput.LineNumber + ": The number of characters should match the width dimension (" + mMetadata.GridWidth + ")."); } for (int x = 0; x < mMetadata.GridWidth; x++) { mGrid[x, y] = line[x]; } } if (y < mMetadata.GridHeight) { throw new ParserException("Unexpected EOF on line " + mInput.LineNumber + ": The number of lines in this section should match the height dimension (" + mMetadata.GridHeight + ")."); } return null; } string ReadEntitySection(char entity) { Dictionary pairs = new Dictionary(); mEntities[entity] = pairs; while (!mInput.End) { string line = mInput.ReadLine(); string[] pair = Parse.KeyValuePair(line); if (pair != null) { pairs[pair[0]] = pair[1]; } else { return line; } } return null; } bool IsLineSignificant(string line) { if (line.Trim().Length == 0 || Parse.IniComment(line) != null) return false; return true; } #endregion #region Private Types /// /// This private class wraps around ContentReader to make it more /// convenient to use it as an input stream reader. /// class LineReader { ContentReader mInput; int mLineNumber = 0; int mExpectedNumberOfLines; public LineReader(ContentReader input) { mInput = input; mExpectedNumberOfLines = mInput.ReadInt32(); } public string ReadLine() { mLineNumber++; return mInput.ReadString(); } public int LineNumber { get { return mLineNumber; } } public bool End { get { return mLineNumber >= mExpectedNumberOfLines; } } } #endregion #region Private Variables Map.Metadata mMetadata; char[,] mGrid; Dictionary> mEntities = new Dictionary>(); LineReader mInput; #endregion } }