using System; using System.Collections.Generic; using System.Globalization; using System.Xml; using Microsoft.Xna.Framework; namespace V3.Map { /// /// Parser for the tmx format of the Tiled Map Editor. /// Reads XML file and returns corresponding data objects. /// public sealed class TiledParser { private string mFileName; // Map Data: public int MapWidth { get; private set; } public int MapHeight { get; private set; } public int TileWidth { get; private set; } public int TileHeight { get; private set; } public SortedList TileSets { get; } = new SortedList(); public List MapLayers { get; } = new List(); public List Areas { get; } = new List(); /// /// Parse the tmx file and hold data in instance properties. /// public void Parse(string fileName) { mFileName = fileName; string directory = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase); string fullPath = directory + "/Content/Maps/" + mFileName + ".tmx"; int p = (int)Environment.OSVersion.Platform; if ((p == 4) || (p == 6) || (p == 128)) // Running on Unix fullPath = fullPath.Substring(5); #if DEBUG Console.WriteLine("Loading Map: " + fullPath); #endif XmlReader reader = XmlReader.Create(fullPath); while (reader.Read()) { if (reader.IsStartElement()) { switch (reader.Name) { case "map": ParseMapData(reader); break; case "tileset": ParseTilesetData(reader); break; case "layer": ParseLayerData(reader); break; case "objectgroup": ParseObjectgroup(reader); break; } } } } private void ParseMapData(XmlReader reader) { while (reader.MoveToNextAttribute()) { switch (reader.Name) { case "width": MapWidth = reader.ReadContentAsInt(); break; case "height": MapHeight = reader.ReadContentAsInt(); break; case "tilewidth": TileWidth = reader.ReadContentAsInt(); break; case "tileheight": TileHeight = reader.ReadContentAsInt(); break; } } reader.MoveToElement(); } private void ParseTilesetData(XmlReader reader) { if (reader.HasAttributes) { List tilesetAttributes = new List(); // Read attributes firstgid, name, tilewidth, tileheight, tilecount, columns. for (int i = 0; i < reader.AttributeCount; i++) { tilesetAttributes.Add(reader[i]); } reader.MoveToElement(); // Read attributes for tileoffset x and y if existing. while (reader.Read()) { if (reader.Name == "tileoffset") { if (reader.IsStartElement()) { for (int i = 0; i < reader.AttributeCount; i++) { tilesetAttributes.Add(reader[i]); } } else { break; } } else if (reader.Name == "tile" || reader.Name == "tileset") { break; } } if (tilesetAttributes.Count == 6) { TileSets.Add(int.Parse(tilesetAttributes[0]), new Tileset(tilesetAttributes[1], int.Parse(tilesetAttributes[2]), int.Parse(tilesetAttributes[3]), int.Parse(tilesetAttributes[5]))); } else if (tilesetAttributes.Count == 8) { TileSets.Add(int.Parse(tilesetAttributes[0]), new Tileset(tilesetAttributes[1], int.Parse(tilesetAttributes[2]), int.Parse(tilesetAttributes[3]), int.Parse(tilesetAttributes[5]), int.Parse(tilesetAttributes[6]), int.Parse(tilesetAttributes[7]))); } else { throw new Exception("Error parsing tileset element in " + mFileName + ".tmx. Does not contain necessary attributes."); } ParseCollisionData(reader, int.Parse(tilesetAttributes[0])); } } private void ParseLayerData(XmlReader reader) { while (reader.MoveToNextAttribute()) { if (reader.Name == "width") { // TODO: Try catching exceptions and throw specific ones. int width = reader.ReadContentAsInt(); reader.MoveToNextAttribute(); int height = reader.ReadContentAsInt(); reader.MoveToElement(); reader.ReadToDescendant("data"); MapLayers.Add(new int[height, width]); int currentLayerIndex = MapLayers.Count - 1; // Map data is in CSV format, therefore split at comma. string[] layerData = reader.ReadString().Split(','); for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { MapLayers[currentLayerIndex][i, j] = int.Parse(layerData[i * width + j]); } } } } reader.MoveToElement(); } private void ParseCollisionData(XmlReader reader, int currentTileset) { do { if (!reader.IsStartElement() && reader.Name == "tileset") { // If the end of the tileset note is reached, leave loop. break; } if (reader.IsStartElement() && reader.Name == "tile" && reader.HasAttributes) { string tileId = reader[0]; reader.MoveToElement(); while (reader.ReadToDescendant("property")) { if (reader.MoveToAttribute("name") && reader.Value == "collision") { reader.MoveToNextAttribute(); string collisionData = reader.Value; Tileset tileset = TileSets[currentTileset]; if (tileId != null) tileset.AddCollisionData(int.Parse(tileId), collisionData); } } } } while (reader.Read()) ; } private void ParseObjectgroup(XmlReader reader) { do { if (!reader.IsStartElement() && reader.Name == "objectgroup") break; if (reader.IsStartElement() && reader.Name == "object") { ParseAreaData(reader); } } while (reader.Read()); } private void ParseAreaData(XmlReader reader) { string type; string name = ""; double density = 0; double chance = 0; Rectangle rectangle; if (reader.AttributeCount == 7) { name = reader[1]; type = reader[2]; if (!(reader[3] != null && reader[4] != null && reader[5] != null && reader[6] != null)) return; rectangle = new Rectangle(int.Parse(reader[3]), int.Parse(reader[4]), int.Parse(reader[5]), int.Parse(reader[6])); } else if (reader.AttributeCount == 6) { type = reader[1]; if (!(reader[2] != null && reader[3] != null && reader[4] != null && reader[5] != null)) return; rectangle = new Rectangle(int.Parse(reader[2]), int.Parse(reader[3]), int.Parse(reader[4]), int.Parse(reader[5])); } else { throw new Exception("Error parsing the map. One of the objects has not the right number of attributes, specifically: " + reader.AttributeCount); } reader.MoveToElement(); while (reader.Read()) { if (!reader.IsStartElement() && reader.Name == "properties") break; if (reader.Name == "property" && reader.HasAttributes) { if (reader[2] == null) return; if (reader[0] == "chance") { chance = double.Parse(reader[2], CultureInfo.InvariantCulture); } else if (reader[0] == "density") { density = double.Parse(reader[2], CultureInfo.InvariantCulture); } reader.MoveToElement(); } } Area area = new Area(type, rectangle, density, chance, name); Areas.Add(area); } } }