using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Xml; namespace Otter { /// /// Class used for importing OgmoProject files quickly, and loading levels created in Ogmo Editor /// (http://ogmoeditor.com) Currently OgmoProjects must export in XML Co-ords for Tiles and Entities, /// and Bitstring for Grids. /// public class OgmoProject { #region Private Fields Dictionary ColliderTags = new Dictionary(); Dictionary levelValueTypes = new Dictionary(); Dictionary assetMappings = new Dictionary(); #endregion #region Public Fields /// /// Determines if grid layers will render in the game. Only applies at loading time. /// public bool DisplayGrids = true; /// /// The default image path to search for tilemaps in. /// public string ImagePath; /// /// Determines if loaded levels will use camera bounds in the Scene. /// public bool UseCameraBounds = true; /// /// Determines if tilemaps are located in an Atlas. /// public bool UseAtlas; /// /// The default background color of the Ogmo Project. /// public Color BackgroundColor; /// /// The default background grid color of the Ogmo Project. /// public Color GridColor; /// /// The known layers loaded from the Ogmo Editor oep file. /// public Dictionary Layers = new Dictionary(); /// /// Mapping the tile layers to file paths. /// public Dictionary TileMaps = new Dictionary(); /// /// The entities stored to create tilemaps and grids. Cleared every time LoadLevel is called. /// public Dictionary Entities = new Dictionary(); /// /// The name of the method to use for creating Entities when loading an .oel file into a Scene. /// public string CreationMethodName = "CreateFromXml"; /// /// The drawing layer to place the first loaded tile map on. /// public int BaseTileDepth; /// /// Determines the drawing layers for each subsequently loaded tile map. For example, the first /// tilemap will be at Layer 0, the second at Layer 100, the third at Layer 200, etc. /// public int TileDepthIncrement = 100; #endregion #region Public Properties /// /// The level data last loaded with LoadLevel() /// public string CurrentLevel { get; private set; } #endregion #region Constructors /// /// Create an OgmoProject from a source .oep file. /// /// The path to the .oep file. /// The default image path to use for loading tilemaps. public OgmoProject(string source, string imagePath = "") { if (!File.Exists(source)) throw new ArgumentException("Ogmo project file could not be found."); if (imagePath == "") { UseAtlas = true; } ImagePath = imagePath; var xmlDoc = new XmlDocument(); xmlDoc.Load(source); BackgroundColor = new Color(xmlDoc["project"]["BackgroundColor"]); GridColor = new Color(xmlDoc["project"]["GridColor"]); var xmlLayers = xmlDoc.GetElementsByTagName("LayerDefinition"); foreach (XmlElement x in xmlLayers) { var layer = new OgmoLayer(x); Layers.Add(layer.Name, layer); } //I dont know if I need to do this var xmlEntities = xmlDoc.GetElementsByTagName("EntityDefinition"); foreach (XmlElement x in xmlEntities) { } var xmlTilesets = xmlDoc.GetElementsByTagName("Tileset"); foreach (XmlElement x in xmlTilesets) { TileMaps.Add(x["Name"].InnerText, x["FilePath"].InnerText); } //var xmlLevelValues = xmlDoc.GetElementsByTagName("ValueDefinitions"); //dirty dirty hack because there should only be one element with that name //and for SOME REASON I can't just grab an XmlElement, I have to grab a NodeList and enumerate it for my element. What gives, microsoft? var xmlLevelValues = xmlDoc.GetElementsByTagName("LevelValueDefinitions")[0] as XmlElement; foreach (XmlElement x in xmlLevelValues.GetElementsByTagName("ValueDefinition")) { levelValueTypes.Add(x.Attributes["Name"].Value, x.Attributes["xsi:type"].Value); } } #endregion #region Private Methods void CreateEntity(XmlElement e, Scene scene) { Type entityType = Util.GetTypeFromAllAssemblies(e.Name); object[] arguments = new object[2]; arguments[0] = scene; arguments[1] = e.Attributes; if (entityType != null) { MethodInfo method = entityType.GetMethod(CreationMethodName, BindingFlags.Static | BindingFlags.Public); if (method != null) { method.Invoke(null, arguments); } else { // Attempt to create with just constructor var x = e.AttributeInt("x"); var y = e.AttributeInt("y"); Entity entity = null; if (entityType.GetConstructor(new Type[] { typeof(int), typeof(int) }) != null) { entity = (Entity)Activator.CreateInstance(entityType, x, y); } else if (entityType.GetConstructor(new Type[] { typeof(int), typeof(int), typeof(OgmoData) }) != null) { entity = (Entity)Activator.CreateInstance(entityType, x, y, new OgmoData(e.Attributes)); } if (entity != null) { scene.Add(entity); } } } } #endregion #region Public Methods /// /// Assign a replacement asset for a Tilemap when LoadLevel is called. /// /// The asset path to find (searches at the end of the string!) /// The full path to replace the matching asset with. public void RemapAsset(string searchPath, string replacement) { assetMappings.Add(searchPath, replacement); } /// /// Get a value from an Ogmo level. /// /// The type of value. /// The name of the value. /// The level data to use. If left blank will use the CurrentLevel. /// The value cast to type T. public T GetValue(string name, string data = "") { if (data == "") { data = CurrentLevel; } var xmlDoc = new XmlDocument(); xmlDoc.LoadXml(data); var xmlLevel = xmlDoc["level"]; var value = xmlLevel.Attributes[name].Value; if (typeof(T) == typeof(Color)) { return (T)Activator.CreateInstance(typeof(T), value.Substring(1, 6)); } else { return (T)Convert.ChangeType(value, typeof(T)); } } /// /// Get a value from an Ogmo level. /// /// The type of value. /// The name of the value. /// The level data to use. If left blank will use the CurrentLevel. /// The value cast to type T. public T GetValue(Enum name, string source = "") { return GetValue(Util.EnumValueToBasicString(name), source); } public T GetValueFromFile(string name, string path) { return (T)GetValue(name, File.ReadAllText(path)); } public string GetLayerData(string name, string data = "") { if (data == "") { data = CurrentLevel; } var xmlDoc = new XmlDocument(); xmlDoc.LoadXml(data); var xmlLevel = xmlDoc["level"]; return xmlLevel[name].InnerText; } /// /// Load level data from a string into a Scene. /// /// The level data to load. /// The Scene to load into. public void LoadLevel(string data, Scene scene) { Entities.Clear(); CurrentLevel = data; var xmlDoc = new XmlDocument(); xmlDoc.LoadXml(data); var xmlLevel = xmlDoc["level"]; scene.Width = int.Parse(xmlDoc["level"].Attributes["width"].Value); scene.Height = int.Parse(xmlDoc["level"].Attributes["height"].Value); int i = 0; foreach (var layer in Layers.Values) { if (layer.Type == "GridLayerDefinition") { var Entity = new Entity(); var grid = new GridCollider(scene.Width, scene.Height, layer.GridWidth, layer.GridHeight); grid.LoadString(xmlLevel[layer.Name].InnerText); if (ColliderTags.ContainsKey(layer.Name)) { grid.AddTag(ColliderTags[layer.Name]); } if (DisplayGrids) { var tilemap = new Tilemap(scene.Width, scene.Height, layer.GridWidth, layer.GridHeight); tilemap.LoadString(xmlLevel[layer.Name].InnerText, layer.Color); Entity.AddGraphic(tilemap); } Entity.AddCollider(grid); scene.Add(Entity); Entities.Add(layer.Name, Entity); } if (layer.Type == "TileLayerDefinition") { var Entity = new Entity(); var xmlTiles = xmlLevel[layer.Name]; var tileset = xmlTiles.Attributes["tileset"].Value; var tilepath = ImagePath + TileMaps[tileset]; foreach(var kv in assetMappings) { var find = kv.Key; var replace = kv.Value; if (tilepath.EndsWith(find)) { tilepath = replace; break; } } var tilemap = new Tilemap(tilepath, scene.Width, scene.Height, layer.GridWidth, layer.GridHeight); var exportMode = xmlTiles.Attributes["exportMode"].Value; switch (exportMode) { case "CSV": tilemap.LoadCSV(xmlTiles.InnerText); break; case "XMLCoords": foreach (XmlElement t in xmlTiles) { tilemap.SetTile(t); } break; } tilemap.Update(); Entity.AddGraphic(tilemap); Entity.Layer = BaseTileDepth - i * TileDepthIncrement; i++; scene.Add(Entity); Entities.Add(layer.Name, Entity); } if (layer.Type == "EntityLayerDefinition") { var xmlEntities = xmlLevel[layer.Name]; if (xmlEntities != null) { foreach (XmlElement e in xmlEntities) { CreateEntity(e, scene); } } } } if (UseCameraBounds) { scene.CameraBounds = new Rectangle(0, 0, scene.Width, scene.Height); scene.UseCameraBounds = true; } } /// /// Load data into a Scene from a source .oel file. /// /// The oel to load. /// The Scene to load into. public void LoadLevelFromFile(string path, Scene scene) { LoadLevel(File.ReadAllText(path), scene); } /// /// Register a collision tag on a grid layer loaded from the oel file. /// /// The tag to use. /// The layer name that should use the tag. public void RegisterTag(int tag, string layerName) { ColliderTags.Add(layerName, tag); } /// /// Register a collision tag on a grid layer loaded from the oel file. /// /// The enum tag to use. (Casts to int!) /// The layer name that should use the tag. public void RegisterTag(Enum tag, string layerName) { RegisterTag(Convert.ToInt32(tag), layerName); } /// /// Get the Entity that was created for a specific Ogmo layer. /// /// The name of the layer to find. /// The Entity created for that layer. public Entity GetEntityFromLayerName(string layerName) { return Entities[layerName]; } /// /// Get a list of all the known layer names from the .oep file. /// /// public List GetLayerNames() { var s = new List(); foreach (var l in Layers) { s.Add(l.Key); } return s; } #endregion } /// /// Class representing a layer loaded from Ogmo. /// public class OgmoLayer { #region Public Fields /// /// The name of the layer. /// public string Name; /// /// The export mode of the layer from Ogmo Editor. /// public string ExportMode; /// /// The type of the layer from Ogmo Editor. /// public string Type; /// /// The width of each grid cell. /// public int GridWidth; /// /// The height of each grid cell. /// public int GridHeight; /// /// The horizontal parallax of the layer. /// public float ScrollX = 1; /// /// The vertical parallax of the layer. /// public float ScrollY = 1; /// /// The color of layer from Ogmo Editor. /// public Color Color; #endregion #region Constructors /// /// Create a new OgmoLayer. /// /// The name of the layer. /// The type of the layer. public OgmoLayer(string name, string type) { Name = name; Type = type; } /// /// Create a new OgmoLayer by parsing an XmlElement. /// /// An XmlElement from an Ogmo Editor project file. public OgmoLayer(XmlElement xml) { Name = xml["Name"].InnerText; Type = xml.Attributes["xsi:type"].Value; GridWidth = int.Parse(xml["Grid"]["Width"].InnerText); GridHeight = int.Parse(xml["Grid"]["Height"].InnerText); ScrollX = int.Parse(xml["ScrollFactor"]["X"].InnerText); ScrollY = int.Parse(xml["ScrollFactor"]["Y"].InnerText); if (Type == "GridLayerDefinition") { Color = new Color(xml["Color"]); } } #endregion } /// /// A simple data class that just extends Dictionary. /// public class OgmoData : Dictionary { public OgmoData(XmlAttributeCollection attributes) { foreach (XmlAttribute attr in attributes) { Add(attr.Name, attr.Value); } } public int GetInt(string key, int onNull) { return this.ValueAsInt(key, onNull); } public bool GetBool(string key, bool onNull) { return this.ValueAsBool(key, onNull); } public float GetFloat(string key, float onNull) { return this.ValueAsFloat(key, onNull); } public Color GetColor(string key, Color onNull) { return this.ValueAsColor(key, onNull); } } }