//------------------------------------------------------------------------------
        // Function: WriteTileLayers
        // Author: nholmes
        // Summary: writes all the tile layer data
        //------------------------------------------------------------------------------
        public bool WriteTileLayers(TileLayer[] tileLayers, int masterTileLayer)
        {
            XmlNode listNode;
            XmlNode childNode;
            XmlAttribute newAttribute;

            // if we haven't got a valid root node, return failure
            if (writeRoot == null) return false;

            // create a node to hold the list of tile layers
            listNode = writeLevelDOM.CreateElement("TileLayers");

            // write the index of the master tile layer as an attribute of the node
            newAttribute = writeLevelDOM.CreateAttribute("MasterTileLayer");
            newAttribute.Value = masterTileLayer.ToString(); ;
            listNode.Attributes.Append(newAttribute);

            // add an attribute to the list node to store the number of tile layers we are going to store
            newAttribute = writeLevelDOM.CreateAttribute("Count");
            newAttribute.Value = tileLayers.Length.ToString();
            listNode.Attributes.Append(newAttribute);

            // create a node for each tile layer to hold it's data
            for (int tileLayer = 0; tileLayer < tileLayers.Length; tileLayer++)
            {
                // create a node for this tile layer
                childNode = writeLevelDOM.CreateElement("TileLayer");

                // write the name of the tile layer as an attribute of the node
                newAttribute = writeLevelDOM.CreateAttribute("Name");
                newAttribute.Value = tileLayers[tileLayer].Name;
                childNode.Attributes.Append(newAttribute);

                // write the index of this tile layer as an attribute of the node
                newAttribute = writeLevelDOM.CreateAttribute("Number");
                newAttribute.Value = tileLayer.ToString();
                childNode.Attributes.Append(newAttribute);

                // write the mode of the tile layer as an attribute of the node
                newAttribute = writeLevelDOM.CreateAttribute("Mode");
                newAttribute.Value = tileLayers[tileLayer].LayerMode.ToString();
                childNode.Attributes.Append(newAttribute);

                // write the target layer of this tile layer as an attribte of the node
                newAttribute = writeLevelDOM.CreateAttribute("Target");
                if (tileLayers[tileLayer].Target == null)
                    newAttribute.Value = "None";
                else
                    newAttribute.Value = tileLayers[tileLayer].Target.Name;
                childNode.Attributes.Append(newAttribute);

                // write the tile set this tile layer uses as an attribute of the node
                newAttribute = writeLevelDOM.CreateAttribute("TileSet");
                newAttribute.Value = tileLayers[tileLayer].TileSet.Name;
                childNode.Attributes.Append(newAttribute);

                // write the tile map this tile layer uses as an attribte of the node
                newAttribute = writeLevelDOM.CreateAttribute("TileMap");
                newAttribute.Value = tileLayers[tileLayer].TileMap.Name;
                childNode.Attributes.Append(newAttribute);

                // write the tint color this tile layer uses as an attribte of the node
                newAttribute = writeLevelDOM.CreateAttribute("TintColor");
                newAttribute.Value = tileLayers[tileLayer].TintColor.A + "," + tileLayers[tileLayer].TintColor.R + "," + tileLayers[tileLayer].TintColor.G + "," + tileLayers[tileLayer].TintColor.B;
                childNode.Attributes.Append(newAttribute);

                // write the position scale this tile layer uses as an attribte of the node
                newAttribute = writeLevelDOM.CreateAttribute("PositionScale");
                newAttribute.Value = tileLayers[tileLayer].PositionScaleX + "," + tileLayers[tileLayer].PositionScaleY;
                childNode.Attributes.Append(newAttribute);

                // write the position offset this tile layer uses as an attribte of the node
                newAttribute = writeLevelDOM.CreateAttribute("PositionOffset");
                newAttribute.Value = tileLayers[tileLayer].PositionOffsetX + "," + tileLayers[tileLayer].PositionOffsetY;
                childNode.Attributes.Append(newAttribute);

                // append the node to the list of tile layer nodes
                listNode.AppendChild(childNode);
            }

            // add the list of tile layers to the docuemnt
            writeRoot.AppendChild(listNode);

            // report success!
            return true;
        }
        //------------------------------------------------------------------------------
        // Function: ReadEditorInfo
        // Author: nholmes
        // Summary: reads the editor related info (background colour, view scales etc)
        //------------------------------------------------------------------------------
        public bool ReadEditorInfo(out Color backgroundColor, out float tileSetViewScale, out float tileLayerViewScale, TileLayer[] tileSetLayers)
        {
            // set some defaults just in case we have a problem and need to bail out...
            backgroundColor = Color.Blue;
            tileSetViewScale = 1.0f;
            tileLayerViewScale = 1.0f;

            // if we haven't got a valid root node, return failure
            if (readRoot == null) return false;

            // find the editor info node
            XmlNode tileLayerNode = readRoot.SelectSingleNode("//EditorInfo");

            // find the background colour attribute
            XmlNode attributeNode = tileLayerNode.Attributes.GetNamedItem("BackgroundColor");

            // was the background colour attribute found ok? return failure if not!
            if (attributeNode == null) return false;

            // read and store the background color
            string dataRef = attributeNode.InnerText;
            backgroundColor = new Color();

            // get the alpha value
            int nextComma = dataRef.IndexOf(',');
            backgroundColor.A = (byte)Convert.ToInt32(dataRef.Remove(nextComma));
            dataRef = dataRef.Remove(0, nextComma + 1);

            // get the red value
            nextComma = dataRef.IndexOf(',');
            backgroundColor.R = (byte)Convert.ToInt32(dataRef.Remove(nextComma));
            dataRef = dataRef.Remove(0, nextComma + 1);

            // get the green value
            nextComma = dataRef.IndexOf(',');
            backgroundColor.G = (byte)Convert.ToInt32(dataRef.Remove(nextComma));
            dataRef = dataRef.Remove(0, nextComma + 1);

            // get the blue value
            backgroundColor.B = (byte)Convert.ToInt32(dataRef);

            // find the tile list view scale attribute
            attributeNode = tileLayerNode.Attributes.GetNamedItem("TileSetViewScale");

            // was the tile list view scale attribute found ok? return failure if not!
            if (attributeNode == null) return false;

            // retrieve and store the tile list view scale
            tileSetViewScale = Convert.ToSingle(attributeNode.Value);

            // find the tile layer view scale attribute
            attributeNode = tileLayerNode.Attributes.GetNamedItem("TileLayerViewScale");

            // was the tile layer view scale attribute found ok? return failure if not!
            if (attributeNode == null) return false;

            // retrieve and store the tile layer view scale
            tileLayerViewScale = Convert.ToSingle(attributeNode.Value);

            // return success!
            return true;
        }
        //------------------------------------------------------------------------------
        // Function: ReadTileLayers
        // Author: nholmes
        // Summary: finds all the tile layers in the loaded level and re-creates them
        //------------------------------------------------------------------------------
        public bool ReadTileLayers(Game game, out TileLayer[] tileLayers, out int masterTileLayer, TileSet[] tileSets, TileMap[] tileMaps)
        {
            // initialy set tileLayers to null, in case there are any errors and we need to bail out early!
            tileLayers = null;

            // set master tile layer to -1, in case there are any errors and we need to bail out early!
            masterTileLayer = -1;

            // if we haven't got a valid root node, return failure
            if (readRoot == null) return false;

            // find the tile layer node
            XmlNode tileLayerNode = readRoot.SelectSingleNode("//TileLayers");

            // find the master tile layer attribute
            XmlNode masterTileLayerNode = tileLayerNode.Attributes.GetNamedItem("MasterTileLayer");

            // was the master tile layer attribute found ok? return failure if not!
            if (masterTileLayerNode == null) return false;

            // store the master tile layer index
            masterTileLayer = Convert.ToInt32(masterTileLayerNode.InnerText);

            // find the number of tile layers attribute
            XmlNode numberOfTileLayersNode = tileLayerNode.Attributes.GetNamedItem("Count");

            // was the number of tile layers attribute found ok? return failure if not!
            if (numberOfTileLayersNode == null) return false;

            // store the number of tile layers
            int numTileLayers = Convert.ToInt32(numberOfTileLayersNode.InnerText);

            // create the tile layer array
            tileLayers = new TileLayer[numTileLayers];

            // create an array to hold the tile layer target names so we can process them once all layers are loaded
            string[] layerTargetNames = new string[numTileLayers];

            // loop through all of the tile maps and re-create them
            for (int tileLayer = 0; tileLayer < numTileLayers; tileLayer++)
            {
                // get the tile layer that corresponds to the current index
                XmlNode tileLayerData = readRoot.SelectSingleNode("//TileLayers/TileLayer[@Number=" + tileLayer + "]");

                // check that the tile layer was found and return failure if not
                if (tileLayerData == null) return false;

                // read the tile layer name
                XmlNode tileLayerNameNode = tileLayerData.Attributes.GetNamedItem("Name");

                // was the name data found ok? return failure if not!
                if (tileLayerNameNode == null) return false;

                // create the tile layer
                tileLayers[tileLayer] = new TileLayer(game, tileLayerNameNode.InnerText);

                // null the tile layer's tile set and tile map pointers in case there is an error and we have to bail out
                tileLayers[tileLayer].TileSet = null;
                tileLayers[tileLayer].TileMap = null;

                // find the tile set data
                XmlNode tileSetData = tileLayerData.Attributes.GetNamedItem("TileSet");

                // was the tile set data found ok? return failure if not!
                if (tileSetData == null) return false;

                // Find the correct tile set in the supplied list of tile sets
                for(int tileSet = 0; tileSet < tileSets.Length; tileSet++)
                {
                    if(tileSets[tileSet].Name == tileSetData.InnerText)
                    {
                        // found the correct tile set - store a reference to the tile set in the layer
                        tileLayers[tileLayer].TileSet = tileSets[tileSet];
                        break;
                    }
                }

                // check that we found a valid tile set - return failure if we didn't!
                if (tileLayers[tileLayer].TileSet == null) return false;

                // find the tile map data
                XmlNode tileMapData = tileLayerData.Attributes.GetNamedItem("TileMap");

                // was the tile map data found ok? return failure if not!
                if (tileMapData == null) return false;

                // Find the correct tile map in the supplied list of tile maps
                for (int tileMap = 0; tileMap < tileMaps.Length; tileMap++)
                {
                    if (tileMaps[tileMap].Name == tileMapData.InnerText)
                    {
                        // found the correct tile map - store a reference to the tile map in the layer
                        tileLayers[tileLayer].TileMap = tileMaps[tileMap];
                        break;
                    }
                }

                // check that we found a valid tile map - return failure if we didn't!
                if (tileLayers[tileLayer].TileMap == null) return false;

                // find the layer mode data
                XmlNode layerModeData = tileLayerData.Attributes.GetNamedItem("Mode");

                // was the layer mode data found ok? return failure if not!
                if (layerModeData == null) return false;

                // set the layer mode according to the data we extracted
                switch (layerModeData.InnerText)
                {
                    case "Static":
                        tileLayers[tileLayer].LayerMode = TileLayerMode.Static;
                        break;

                    case "Forced":
                        tileLayers[tileLayer].LayerMode = TileLayerMode.Forced;
                        break;

                    case "Follow":
                        tileLayers[tileLayer].LayerMode = TileLayerMode.Follow;
                        break;

                    case "Relative":
                        tileLayers[tileLayer].LayerMode = TileLayerMode.Relative;
                        break;

                    case "Scaled":
                        tileLayers[tileLayer].LayerMode = TileLayerMode.Scaled;
                        break;

                    default:

                        // something has gone wrong - return failure!
                        return false;
                }

                // find the layer target data
                XmlNode layerTargetData = tileLayerData.Attributes.GetNamedItem("Target");

                // was the layer target data found ok? return failure if not!
                if (layerTargetData == null) return false;

                // read and store the layer target name so we can fix these up once all layers are loaded
                layerTargetNames[tileLayer] = layerTargetData.InnerText;

                // find the tint color data
                XmlNode tintColorData = tileLayerData.Attributes.GetNamedItem("TintColor");

                // was the tint color data found ok? return failure if not!
                if (tintColorData == null) return false;

                // read and store the tint color
                string dataRef = tintColorData.InnerText;
                Color tintColor = new Color();

                // get the alpha value
                int nextComma = dataRef.IndexOf(',');
                tintColor.A = (byte)Convert.ToInt32(dataRef.Remove(nextComma));
                dataRef = dataRef.Remove(0, nextComma + 1);

                // get the red value
                nextComma = dataRef.IndexOf(',');
                tintColor.R = (byte)Convert.ToInt32(dataRef.Remove(nextComma));
                dataRef = dataRef.Remove(0, nextComma + 1);

                // get the green value
                nextComma = dataRef.IndexOf(',');
                tintColor.G = (byte)Convert.ToInt32(dataRef.Remove(nextComma));
                dataRef = dataRef.Remove(0, nextComma + 1);

                // get the blue value
                tintColor.B = (byte)Convert.ToInt32(dataRef);

                // store the tint color
                tileLayers[tileLayer].TintColor = tintColor;

                // find the position scale data
                XmlNode positionScaleData = tileLayerData.Attributes.GetNamedItem("PositionScale");

                // was the position scale data found ok? return failure if not!
                if (positionScaleData == null) return false;

                // read and store the position scale
                dataRef = positionScaleData.InnerText;

                // get and store the X value
                nextComma = dataRef.IndexOf(',');
                tileLayers[tileLayer].PositionScaleX = Convert.ToSingle(dataRef.Remove(nextComma));
                dataRef = dataRef.Remove(0, nextComma + 1);

                // get and store the Y value
                tileLayers[tileLayer].PositionScaleY = Convert.ToSingle(dataRef);

                // find the position scale data
                XmlNode positionOffsetData = tileLayerData.Attributes.GetNamedItem("PositionOffset");

                // was the position offset data found ok? return failure if not!
                if (positionOffsetData == null) return false;

                // read and store the position offset
                dataRef = positionOffsetData.InnerText;

                // get and store the X value
                nextComma = dataRef.IndexOf(',');
                tileLayers[tileLayer].PositionOffsetX = Convert.ToSingle(dataRef.Remove(nextComma));
                dataRef = dataRef.Remove(0, nextComma + 1);

                // get and store the Y value
                tileLayers[tileLayer].PositionOffsetY = Convert.ToSingle(dataRef);
            }

            // now all the layers are loaded and created we need to fix up the layer targets
            for (int tileLayer = 0; tileLayer < numTileLayers; tileLayer++)
            {
                if (layerTargetNames[tileLayer] == "None")
                    tileLayers[tileLayer].Target = null;
                else
                {
                    int searchLayer;

                    // search all of the tile layers for the layer this layer wishes to target
                    for (searchLayer = 0; searchLayer < numTileLayers; searchLayer++)
                    {
                        // check if this layer matches the name we are looking for
                        if (tileLayers[searchLayer].Name == layerTargetNames[tileLayer])
                        {
                            // found the match - store it and bail out
                            tileLayers[tileLayer].Target = tileLayers[searchLayer];
                            break;
                        }
                    }

                    // check to see if we failed to find a batch and bail out with failure if so
                    if (searchLayer == numTileLayers) return false;
                }
            }

            // return success
            return true;
        }
        //------------------------------------------------------------------------------
        // Function: Update
        // Author: nholmes
        // Summary: helper function to update the positions of all other tile layers
        //          relative to the master tile layer. NOTE: assumes that you only call
        //          this function on the master tile layer!
        //------------------------------------------------------------------------------
        public void UpdateAllLayers(TileLayer[] tileLayers, int masterLayerIndex)
        {
            int tileLayer;

            // clear the update status of all tile layers
            for (tileLayer = 0; tileLayer < tileLayers.Length; tileLayer++) tileLayers[tileLayer].updated = false;

            // set the updated status of the master tileset to true
            tileLayers[masterLayerIndex].updated = true;

            // continue to process throught all the layers until they have all been updated (allows for crazy out-of-order map layers!)
            while (true)
            {
                // spin through all of the supplied layers and update any that refer to the master layer
                for (tileLayer = 0; tileLayer < tileLayers.Length; tileLayer++)
                {
                    // if this layer has been updated, skip to the next one
                    if (tileLayers[tileLayer].updated == true) continue;

                    // not updated yet, see if this layer has a target
                    if (tileLayers[tileLayer].target != null)
                    {
                        // has the target been updated already? bail out if not..
                        if (tileLayers[tileLayer].target.updated == false) continue;

                        // ok, time to update this layer!
                        switch (tileLayers[tileLayer].mode)
                        {
                            case TileLayerMode.Static:

                                // no nothing :)
                                break;

                            case TileLayerMode.Forced:

                                // use the position scale values to move the layer by a fixed amount each frame
                                tileLayers[tileLayer].position = tileLayers[tileLayer].PositionScale;
                                break;

                            case TileLayerMode.Follow:

                                // exactly match the target position
                                tileLayers[tileLayer].position = tileLayers[tileLayer].target.Position;
                                break;

                            case TileLayerMode.Relative:

                                // move relative to the target's position taking into account the size of both layers
                                tileLayers[tileLayer].position.X = tileLayers[tileLayer].target.Position.X * ((float)tileLayers[tileLayer].tileMap.Width / (float)tileLayers[tileLayer].target.tileMap.Width);
                                tileLayers[tileLayer].position.Y = tileLayers[tileLayer].target.Position.Y * ((float)tileLayers[tileLayer].tileMap.Height / (float)tileLayers[tileLayer].target.tileMap.Height);
                                break;

                            case TileLayerMode.Scaled:

                                // move as a scaled amount of the target's position
                                tileLayers[tileLayer].position = tileLayers[tileLayer].target.Position * tileLayers[tileLayer].positionScale;
                                break;
                        }

                        // clamp the layer to it's allowed bounds
                        tileLayers[tileLayer].ClampLayerPosition();

                        // flag this layer as updated
                        tileLayers[tileLayer].updated = true;
                    }
                    else
                    {
                        // check to see if this layer is a forced scrolling layer
                        if (mode == TileLayerMode.Forced)
                        {
                            // move the tile layer by the scale X and scale Y amounts
                            tileLayers[tileLayer].Position += tileLayers[tileLayer].PositionScale;

                            // clamp the layer's new position
                            tileLayers[tileLayer].ClampLayerPosition();
                        }

                        // if we get here then this layer has been update succesfully - flag it
                        tileLayers[tileLayer].updated = true;
                    }
                }

                // check the status of all the layers to see if we have finished
                for (tileLayer = 0; tileLayer < tileLayers.Length; tileLayer++)
                {
                    // bail out if we find a layer that has not yet been updated
                    if (tileLayers[tileLayer].updated == false) break;
                }

                // are they all completed? if so, break out of the while loop!
                if (tileLayer == tileLayers.Length) break;
            }
        }
        //------------------------------------------------------------------------------
        // Function: TileLayer
        // Author: nholmes
        // Summary: constructor to use when data is available - sets the map data to use,
        //          graphics to use, the size of the tiles and how the layer moves in
        //          relation to it's parent position
        //------------------------------------------------------------------------------
        public TileLayer(string layerName, TileSet tileSet, TileMap tileMap, TileLayerMode layerMode, TileLayer targetLayer, float displayScale, Vector2 positionScale, Vector2 positionOffset, Vector2 displaySize, Color tintColor)
        {
            // store the layer's name
            this.name = layerName;

            // store the name of the tile set and a reference to them
            tileSetName = tileSet.Name;
            this.tileSet = tileSet;

            // store the name of the tile map and a reference to them
            tileMapName = tileMap.Name;
            this.tileMap = tileMap;

            // store the layer mode
            this.mode = layerMode;

            // store the target layer
            target = targetLayer;

            // clear the updated status
            updated = false;

            // store the scale
            this.displayScale = displayScale;

            // store the position scale (this will be ignored if position mode is anything other than 'Scaled'
            this.positionScale = positionScale;

            // store the position offset (used by all targeted layer modes)
            this.positionOffset = positionOffset;

            // precalculate values used for displaying the tile layer
            SetDisplaySize(displaySize);

            // set the tint color
            this.tintColor = tintColor;
        }
        //------------------------------------------------------------------------------
        // Function: TileLayer
        // Author: nholmes
        // Summary: constructor to use to create a blank tile layer
        //------------------------------------------------------------------------------
        public TileLayer(Game game, string layerName)
        {
            // get a handle to the display manager service
            displayManager = (DisplayManager)game.Services.GetService(typeof(DisplayManager));

            // store the layer's name
            name = layerName;

            // store the name of the tile set and a reference to them
            tileSetName = "";
            tileSet = null;

            // store the name of the tile map and a reference to them
            tileMapName = "";
            tileMap = null;

            // store the layer mode
            mode = TileLayerMode.Follow;

            // set the target layer to be undefined
            target = null;

            // default tint color is white
            tintColor = new Color(255, 255, 255, 255);

            // clear the updated status
            updated = false;

            // store the scale
            displayScale = 1.0f;

            // store the position scale (this will be ignored if position mode is anything other than 'Scaled'
            positionScale = new Vector2(1.0f, 1.0f);

            // store the position offset (used by all targeted layer modes)
            positionOffset = new Vector2(0.0f, 0.0f);
        }