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; using System.Reflection; 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 Protected Methods protected override Map Read(ContentReader input, Map existingInstance) { mImpl = new Impl(input); return mImpl.GetMap(); } #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; } } } /// /// This class is the actual implementation. The implementation is wrapped /// in a subclass because the invoker seems to only be able to invoke public /// methods, and this needs to invoke methods that shouldn't be public. /// class Impl { public Impl(ContentReader input) { mInput = new LineReader(input); ReadSectionHeaders(); PostProcess(); } public Map GetMap() { return new Map(mMetadata, mGrid, mDefaultTile, mEntities, mPlayerPositions); } public 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 && IsValidEntityIdentifier(section[0])) { line = ReadEntitySection(section[0]); } else { throw new Exception("Unexpected section on line " + mInput.LineNumber + ": " + section); } } else { throw new Exception("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) { try { string methodName = "set_" + pair[0].ToLowerInvariant(); object[] args = new object[1]; args[0] = pair[1]; GetType().InvokeMember(methodName, BindingFlags.InvokeMethod, null, this, args); } #pragma warning disable 0168 catch (System.MissingMethodException ex) #pragma warning restore 0168 { throw new Exception("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 Exception("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 Exception("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 Exception("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 identifier) { Dictionary pairs = new Dictionary(); mEntitySections[identifier] = 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; } void PostProcess() { if (mMetadata == null || mGrid == null) { throw new Exception("Missing a required section. Make sure the metadata and grid are there."); } mEntities = new List(); mPlayerPositions = new Point[mMetadata.NumPlayers.Max() + 1]; // create entities defined completely foreach (char identifier in mEntitySections.Keys) { Dictionary pairs = mEntitySections[identifier]; if (pairs.ContainsKey("create")) { string[] list = Parse.List(pairs["create"]); foreach (string positionString in list) { Point? position = Parse.Coordinates(positionString); if (position != null) { Map.RawEntity createEntity = new Map.RawEntity(); createEntity.Id = identifier; createEntity.Position = position.Value; createEntity.Attributes = pairs; mEntities.Add(createEntity); } else { throw new Exception("Unexpected value of key `create' defined for entity " + identifier + "."); } } pairs.Remove("create"); } } // create entities with positions defined on the grid // and get player starting positions for (int x = 0; x < mMetadata.GridWidth; x++) { for (int y = 0; y < mMetadata.GridHeight; y++) { char identifier = mGrid[x, y]; if (IsValidEntityIdentifier(identifier)) { if (mEntitySections.ContainsKey(identifier)) { Map.RawEntity createEntity = new Map.RawEntity(); createEntity.Id = identifier; createEntity.Position = new Point(x, y); createEntity.Attributes = mEntitySections[identifier]; mEntities.Add(createEntity); } else { throw new Exception("Unexpected entity (" + identifier + ") placed on the grid at [" + x + "," + y + "] but not defined."); } mGrid[x, y] = mDefaultTile; } else if ('1' <= identifier && identifier <= '9') { int playerNum = identifier - 48; if (playerNum < mPlayerPositions.Count()) { mPlayerPositions[playerNum] = new Point(x, y); } mGrid[x, y] = mDefaultTile; } } } // check if all needed player positions are defined for (int i = 1; i < mPlayerPositions.Count(); i++) { if (mPlayerPositions[i] == default(Point)) { throw new Exception("Not enough player positions were defined on the grid; " + "you are missing a spot for player " + i + "."); } } } bool IsLineSignificant(string line) { if (line.Trim().Length == 0 || Parse.IniComment(line) != null) return false; return true; } bool IsValidEntityIdentifier(char id) { if (('a' <= id && id <= 'z') || ('A' <= id && id <= 'Z')) return true; return false; } public void set_author(string atom) { string value = Parse.String(atom); if (value != null) mMetadata.Author = value; else throw new Exception("Unexpected value on line " + mInput.LineNumber + ": " + atom); } public void set_levelname(string atom) { string value = Parse.String(atom); if (value != null) mMetadata.Name = value; else throw new Exception("Unexpected value on line " + mInput.LineNumber + ": " + atom); } public void set_next(string atom) { string value = Parse.String(atom); if (value != null) mMetadata.Next = value; else throw new Exception("Unexpected value on line " + mInput.LineNumber + ": " + atom); } public void set_type(string atom) { Map.Mode? value = Parse.Constant(atom); if (value != null) mMetadata.Type = value.Value; else throw new Exception("Unexpected type on line " + mInput.LineNumber + ": " + atom); } public void set_dimensions(string atom) { Point? dimensions = Parse.Coordinates(atom); if (dimensions != null) { mMetadata.GridWidth = dimensions.Value.X; mMetadata.GridHeight = dimensions.Value.Y; if (mMetadata.GridWidth <= 0 || mMetadata.GridHeight <= 0) { throw new Exception("Invalid dimensions on line " + mInput.LineNumber + ": " + atom); } } else { throw new Exception("Unexpected value on line " + mInput.LineNumber + ": " + atom); } } public void set_tileset(string atom) { string value = Parse.String(atom); if (value != null) mMetadata.Tileset = value; else throw new Exception("Unexpected tileset on line " + mInput.LineNumber + ": " + atom); } public void set_defaulttile(string atom) { char? value = Parse.Char(atom); if (value != null) mDefaultTile = value.Value; else throw new Exception("Unexpected tile value on line " + mInput.LineNumber + ": " + atom); } public void set_numplayers(string atom) { string[] list = Parse.List(atom); if (list != null) { foreach (string item in list) { int[] range = Parse.Range(item); if (range != null) { for (int i = range[0]; i <= range[1]; i++) { mMetadata.NumPlayers.Add(i); } continue; } int? integer = Parse.Integer(item); if (integer != null) { mMetadata.NumPlayers.Add(integer.Value); continue; } throw new Exception("Unexpected atom on line " + mInput.LineNumber + ": " + item); } if (mMetadata.NumPlayers.Count == 0) { throw new Exception("No numbers given on line " + mInput.LineNumber + ": " + atom); } } else { throw new Exception("Unexpected value on line " + mInput.LineNumber + ": " + atom); } } Map.Metadata mMetadata; char[,] mGrid; List mEntities; Point[] mPlayerPositions; char mDefaultTile = ' '; Dictionary> mEntitySections = new Dictionary>(); LineReader mInput; } #endregion #region Private Variables Impl mImpl; #endregion } }