Engine – Map.cs

 

#region Using Statements
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using ParadigmEngine.DeferredRenderer;
#endregion

namespace ParadigmEngine.TileEngine
{
    /// <summary>
    /// Defines what part of the map we are drawing
    /// </summary>
    public enum MapDraw
    {
        Normal,
        Foreground,
    }

    /// <summary>
    /// Defines which texture we are drawing
    /// </summary>
    public enum MapTexture
    {
        Color,
        Normals,
        Depth,
    }

    public class Map
    {
        #region Fields
        // lists holds each row from map xml
        private List<List<string>> layers = new List<List<string>>();
        private List<bool> foregroundLayers = new List<bool>();
        private List<string> hazardLayer = new List<string>();
        private List<string> collisionLayer = new List<string>();
        private List<string> terrainLayer = new List<string>();

        private List<Tile> tileData = new List<Tile>();
        private MaterialResource tilesetTextures = new MaterialResource();

        private bool isInitialized = false;
        #endregion

        #region Serialized Attributes
        public string AssetType { get; set; }
        public string ID { get; set; }
        public Color BgColor { get; set; }
        public string TileSet { get; set; }
        public int TilesetWidth { get; set; }
        public int TilesetHeight { get; set; }
        public int Tile_Size { get; set; }

        /// <summary>
        /// Width in tiles
        /// </summary>
        public int Width { get; set; }

        /// <summary>
        /// Height in tiles
        /// </summary>
        public int Height { get; set; }

        public LightParams LightParams { get; set; }
        public List<PointLight> LightList { get; set; }
        public List<AnimatedTileInfo> AnimatedTileList { get; set; }
        public List<MapEntityInfo> SpawnList { get; set; }
        public List<ParticleEmitterInfo> ParticleEmitterList { get; set; }
        public List<PortalInfo> PortalList { get; set; }
        public List<ShaderRegionInfo> ShaderRegionList { get; set; }
        public List<TerrainInfo> TerrainList { get; set; }
        public List<MapEventInfo> EventList { get; set; }

        /// <summary>
        /// Map Layers
        /// </summary>
        public List<List<string>> Layers
        {
            get { return layers; }
            set { layers = value; }
        }

        /// <summary>
        /// Foreground layer indices
        /// </summary>
        public List<bool> ForegroundLayers
        {
            get { return foregroundLayers; }
            set { foregroundLayers = value; }
        }

        /// <summary>
        /// Hazard layers
        /// </summary>
        public List<string> HazardLayer
        {
            get { return hazardLayer; }
            set { hazardLayer = value; }
        }

        /// <summary>
        /// Collision layer
        /// </summary>
        public List<string> CollisionLayer
        {
            get { return collisionLayer; }
            set { collisionLayer = value; }
        }

        /// <summary>
        /// Collision layer
        /// </summary>
        public List<string> TerrainLayer
        {
            get { return terrainLayer; }
            set { terrainLayer = value; }
        }
        #endregion

        #region Unserialized Attributes

        [ContentSerializerIgnore]
        public MaterialResource TilesetTextures
        {
            get { return tilesetTextures; }
        }

        [ContentSerializerIgnore]
        [XmlIgnore()]
        public List<Tile> Tiles
        {
            get { return tileData; }
        }

        [ContentSerializerIgnore]
        [XmlIgnore()]
        public int TilesetXCount { get; set; }

        [ContentSerializerIgnore]
        [XmlIgnore()]
        public int TilesetYCount { get; set; }

        [ContentSerializerIgnore]
        [XmlIgnore()]
        public bool IsInitialized
        {
            get { return isInitialized; }
        }

        [ContentSerializerIgnore]
        public int WidthInPixels
        {
            get
            {
                return (Width * Tile_Size);
            }
        }

        [ContentSerializerIgnore]
        public int HeightInPixels
        {
            get
            {
                return (Height * Tile_Size);
            }
        }
        #endregion

        #region Initialize Methods
        /// <summary>
        /// Initialize map
        /// </summary>
        public void Initialize(ContentManager content, GraphicsDevice graphicsDevice)
        {
            Texture2D tileColorMap = null;
            Texture2D tileNormalMap = null;
            Texture2D tileDepthMap = null;

            tileColorMap = content.Load<Texture2D>(TileSet);

            tilesetTextures.ColorMap = tileColorMap;

            // attempt to load the normals
            try
            {
                tileNormalMap = content.Load<Texture2D>(TileSet + "_Normal");
            }
            catch (System.Exception)
            {
                // upon failure, create a transparent texture
                tileNormalMap = new Texture2D(graphicsDevice, tileColorMap.Width, tileColorMap.Height,
                    1, TextureUsage.None, SurfaceFormat.Color);

                // set a color to the amount of pixels in the texture
                Color[] texture = new Color[tileColorMap.Width * tileColorMap.Height];

                Color transparent = new Color(0, 0, 0, 0);

                for (int i = 0; i < texture.Length; i++)
                {
                    texture[i] = transparent;
                }
                tileNormalMap.SetData(texture);
            }
            finally
            {
                tilesetTextures.NormalMap = tileNormalMap;
            }

            // attempt to load the depth
            try
            {
                tileDepthMap = content.Load<Texture2D>(TileSet + "_Depth");
            }
            catch (System.Exception)
            {
                // upon failure, create a transparent texture
                tileDepthMap = new Texture2D(graphicsDevice, tileColorMap.Width, tileColorMap.Height,
                    1, TextureUsage.None, SurfaceFormat.Color);

                // set a color to the amount of pixels in the texture
                Color[] texture = new Color[tileColorMap.Width * tileColorMap.Height];

                Color black = new Color(0, 0, 0, 255);

                for (int i = 0; i < texture.Length; i++)
                {
                    texture[i] = black;
                }
                tileDepthMap.SetData(texture);
            }
            finally
            {
                tilesetTextures.DepthMap = tileDepthMap;
            }

            FindTilesetDimensions();
            CreateTiles();
            LoadAnimatedTiles(content);
            isInitialized = true;
        }

        /// <summary>
        /// Initialize map
        /// </summary>
        public void Initialize()
        {
            FindTilesetDimensions();
            CreateTiles();
            isInitialized = true;
        }

        /// <summary>
        /// Manually set tileset color map instead of loading it with initialize method
        /// </summary>
        public void SetColorTexture(Texture2D texture)
        {
            this.tilesetTextures.ColorMap = texture;
        }

        /// <summary>
        /// Manually set tileset normal map instead of loading it with initialize method
        /// </summary>
        public void SetNormalTexture(Texture2D texture)
        {
            this.tilesetTextures.NormalMap = texture;
        }

        /// <summary>
        /// Manually set tileset depth map instead of loading it with initialize method
        /// </summary>
        public void SetDepthTexture(Texture2D texture)
        {
            this.tilesetTextures.DepthMap = texture;
        }

        #endregion

        #region Public Methods
        public Map()
        {
            AnimatedTileList = new List<AnimatedTileInfo>();
            SpawnList = new List<MapEntityInfo>();
            ParticleEmitterList = new List<ParticleEmitterInfo>();
            PortalList = new List<PortalInfo>();
            ShaderRegionList = new List<ShaderRegionInfo>();
            TerrainList = new List<TerrainInfo>();
            EventList = new List<MapEventInfo>();

            LightParams = new LightParams();
            LightList = new List<PointLight>();
        }

        /// <summary>
        /// Update all animated tiles
        /// </summary>
        /// <param name="gameTime"></param>
        public void Update(GameTime gameTime)
        {
            foreach (Tile tile in tileData)
                tile.Update(gameTime);
        }

        /// <summary>
        /// Draws the map
        /// </summary>
        public void Draw(GameTime gameTime, SpriteBatch spriteBatch, MapDraw mapDraw, MapTexture mapTexture)
        {
            foreach (Tile tile in tileData)
                tile.Draw(gameTime, spriteBatch, mapDraw, mapTexture);
        }

        /// <summary>
        /// Editor Use:  Draws the map, with the option for foreground highlighting and layer occlusions
        /// </summary>
        /// <param name="spriteBatch"></param>
        /// <param name="mapDraw"></param>
        /// <param name="foregroundHightlight"></param>
        /// <param name="currentLayer"></param>
        public void Draw(GameTime gameTime, SpriteBatch spriteBatch, MapDraw mapDraw, MapTexture mapTexture, bool foregroundHightlight, int currentLayer)
        {
            foreach (Tile tile in tileData)
                tile.Draw(gameTime, spriteBatch, mapDraw, mapTexture, foregroundHightlight, currentLayer);
        }

        /// <summary>
        /// Gets the Collision Type of tile at x,y
        /// </summary>
        public TileCollision GetCollision(int x, int y)
        {
            // Allow passing through level sides to account for portals
            if (x < 0 || x >= Width)
                return TileCollision.Passable;

            // Allow jumping past the level top and falling through the bottom.
            if (y < 0 || y >= Height)
                return TileCollision.Passable;

            int tileID = (y * Width) + x;

            return tileData[tileID].CollisionType;
        }

        /// <summary>
        /// Check and see if a tile causes damage
        /// </summary>
        public bool GetHazardCollision(int x, int y)
        {
            if (x < 0 || x >= Width)
                return false;
            if (y < 0 || y >= Height)
                return false;

            int tileID = (y * Width) + x;

            return tileData[tileID].IsHazard;
        }

        /// <summary>
        /// Return the terrain type associated with a tile
        /// </summary>
        public int GetTerrainType(int x, int y)
        {
            if (x < 0 || x >= Width)
                return -1;
            if (y < 0 || y >= Height)
                return -1;

            int tileID = (y * Width) + x;

            return tileData[tileID].TerrainIndex;
        }

        /// <summary>
        /// Performs a check to see if we have collided with a portal.
        /// </summary>
        /// <param name="boundingBox">Entity Bounding Box</param>
        /// <returns>
        /// Returns the index of the portal from PortalInfoList is there is a collision
        /// Returns -1 if no collision has occured
        /// </returns>
        public int GetPortalCollision(Rectangle boundingBox)
        {
            for (int i = 0; i < PortalList.Count; i++)
            {
                Rectangle portalBounds = new Rectangle();
                PortalInfo portal = PortalList[i];

                switch (portal.inDirection)
                {
                    case PortalDirection.Left:
                        portalBounds.X = portal.boundingBox.X + portal.boundingBox.Width - 1;
                        portalBounds.Y = portal.boundingBox.Y;
                        portalBounds.Width = 1;
                        portalBounds.Height = portal.boundingBox.Height;

                        if (boundingBox.Intersects(portalBounds))
                            return i;
                        break;

                    case PortalDirection.Right:
                        portalBounds.X = portal.boundingBox.X + 1;
                        portalBounds.Y = portal.boundingBox.Y;
                        portalBounds.Width = 1;
                        portalBounds.Height = portal.boundingBox.Height;

                        if (boundingBox.Intersects(portalBounds))
                            return i;
                        break;

                    case PortalDirection.Bottom:
                        portalBounds.X = portal.boundingBox.X;
                        portalBounds.Y = portal.boundingBox.Y + 1;
                        portalBounds.Width = portal.boundingBox.Width;
                        portalBounds.Height = 1;

                        if (boundingBox.Intersects(portalBounds))
                            return i;
                        break;

                    case PortalDirection.Top:
                        portalBounds.X = portal.boundingBox.X;
                        portalBounds.Y = portal.boundingBox.Y + portal.boundingBox.Height - 1;
                        portalBounds.Width = portal.boundingBox.Width; ;
                        portalBounds.Height = 1;

                        if (boundingBox.Intersects(portalBounds))
                            return i;
                        break;
                }
            }
            return -1;
        }

        /// <summary>
        /// Get the bounding box of a portal
        /// </summary>
        /// <param name="portalIndex">Index of portal</param>
        /// <returns>
        /// Returns the bounding box of the specified portal, or an empty rectangle if there is an error
        /// </returns>
        public Rectangle GetPortalBounds(int portalIndex)
        {
            // if the index if out of range, return an empty rectangle
            if (portalIndex >= PortalList.Count)
                return Rectangle.Empty;

            Rectangle portalBounds = new Rectangle();
            PortalInfo portal = PortalList[portalIndex];

            switch (portal.inDirection)
            {
                case PortalDirection.Left:
                    portalBounds.X = portal.boundingBox.X + portal.boundingBox.Width - 1;
                    portalBounds.Y = portal.boundingBox.Y;
                    portalBounds.Width = 1;
                    portalBounds.Height = portal.boundingBox.Height;

                    return portalBounds;

                case PortalDirection.Right:
                    portalBounds.X = portal.boundingBox.X + 1;
                    portalBounds.Y = portal.boundingBox.Y;
                    portalBounds.Width = 1;
                    portalBounds.Height = portal.boundingBox.Height;

                    return portalBounds;

                case PortalDirection.Bottom:
                    portalBounds.X = portal.boundingBox.X;
                    portalBounds.Y = portal.boundingBox.Y + 1;
                    portalBounds.Width = portal.boundingBox.Width;
                    portalBounds.Height = 1;

                    return portalBounds;

                case PortalDirection.Top:
                    portalBounds.X = portal.boundingBox.X;
                    portalBounds.Y = portal.boundingBox.Y + portal.boundingBox.Height - 1;
                    portalBounds.Width = portal.boundingBox.Width; ;
                    portalBounds.Height = 1;

                    return portalBounds;
            }

            return Rectangle.Empty;
        }

        /// <summary>
        /// Get a tiles location in world space
        /// </summary>
        /// <param name="x">X coord</param>
        /// <param name="y">Y coord</param>
        /// <returns></returns>
        public Vector2 GetTileLocation(int x, int y)
        {
            int tileID = (y * Width) + x;
            return tileData[tileID].Location;
        }

        /// <summary>
        /// Gets the bounding rectangle of a tile in world space.
        /// </summary>
        public Rectangle GetBounds(int x, int y)
        {
            return new Rectangle(x * Tile_Size, y * Tile_Size, Tile_Size, Tile_Size);
        }

        /// <summary>
        /// Access a tile directly
        /// </summary>
        /// <param name="x">X coord</param>
        /// <param name="y">Y coord</param>
        /// <returns>Tile</returns>
        public Tile GetTile(int x, int y)
        {
            int tileID = (y * Width) + x;

            try
            {
                return tileData[tileID];
            }
            catch (ArgumentOutOfRangeException)
            {
                return null;
            }
        }
        #endregion

        #region Private Methods
        private List<int> ConvertSpawnLayer(List<string> layer)
        {
            // create a new layer
            List<int> newLayer = new List<int>();

            foreach (String entry in layer)
            {
                string[] exploded = entry.Split(new Char[] { ',' });
                for (int i = 0; i < exploded.Length; i++)
                {
                    newLayer.Add((int)Convert.ToDouble(exploded[i]));
                }
            }
            return newLayer;
        }

        /// <summary>
        /// This converts comma separated string layer data from XML into a numeric list
        /// </summary>
        private List<int> ConvertSpriteLayer(List<string> layer)
        {
            // create a new layer
            List<int> newLayer = new List<int>();

            foreach (String entry in layer)
            {
                string[] exploded = entry.Split(new Char[] { ',' });
                for (int i = 0; i < exploded.Length; i++)
                {
                    newLayer.Add((int)Convert.ToDouble(exploded[i]));
                }
            }
            return newLayer;
        }

        /// <summary>
        /// This converts comma separated string layer data from xml into a numeric list
        /// </summary>
        private List<int> ConvertCollisionLayer(List<string> layer)
        {
            // create a new layer
            List<int> newLayer = new List<int>();

            foreach (String entry in layer)
            {
                string[] exploded = entry.Split(new Char[] { ',' });
                for (int i = 0; i < exploded.Length; i++)
                {
                    newLayer.Add((int)Convert.ToDouble(exploded[i]));
                }
            }
            return newLayer;
        }

        /// <summary>
        /// Find and set the dimensions of the tileset in tiles
        /// </summary>
        private void FindTilesetDimensions()
        {
            TilesetXCount = TilesetWidth / Tile_Size;
            TilesetYCount = TilesetHeight / Tile_Size;
        }

        /// <summary>
        /// Create Tile Data for Map
        /// </summary>
        private void CreateTiles()
        {
            // two dimensional list of lists for multiple map layers
            List<List<int>> mapData = new List<List<int>>();

            // iterate through each layer and add to the data list
            foreach (List<string> layer in layers)
            {
                mapData.Add(ConvertSpriteLayer(layer));
            }

            // add the hazard and collision layers
            mapData.Add(ConvertSpriteLayer(hazardLayer));
            mapData.Add(ConvertCollisionLayer(collisionLayer));
            mapData.Add(ConvertSpriteLayer(terrainLayer));

            int totalTiles = Width * Height;

            // iterate through every tile
            for (int i = 0; i < totalTiles; i++)
            {
                List<int> layerData = new List<int>();

                // iterate through each layer, and add the layer data for that tile to a list
                foreach (List<int> layer in mapData)
                {
                    layerData.Add(layer[i]);
                }

                Tile tile = new Tile(i, layerData, this);
                tileData.Add(tile);
            }
        }

        /// <summary>
        /// Load all animated tiles from map
        /// </summary>
        private void LoadAnimatedTiles(ContentManager content)
        {
            AnimatedTileDesc tileDesc;
            Texture2D animationTex;

            string tileResource = "";

            for (int i = 0; i < AnimatedTileList.Count; i++)
            {
                AnimatedTile animatedTile;

                // get entity directory and filename
                tileResource = AnimatedTileList[i].TileResource;

                // load tile info from resource
                tileDesc = content.Load<AnimatedTileDesc>(@"Animated Tiles\" + tileResource);

                // load texture from desc
                animationTex = content.Load<Texture2D>(tileDesc.Texture.Substring(1, tileDesc.Texture.Length - 5));

                // create new tile and initialize
                animatedTile = new AnimatedTile();
                animatedTile.ResourcePath = tileResource;
                animatedTile.WorldPosition = AnimatedTileList[i].SpawnPoint;
                animatedTile.TilePosition = AnimatedTileList[i].SpawnTile;
                animatedTile.Layer = AnimatedTileList[i].Layer;

                animatedTile.Initialize(animationTex, tileDesc);

                tileData[AnimatedTileList[i].ID].AddAnimatedTile(animatedTile);
            }
        }

        /// <summary>
        /// Update all animated tiles that need updating
        /// </summary>
        /// <param name="tileDesc">tileDesc structure containing description of tiles that need updating</param>
        /// <param name="animationTex">New tile texture</param>
        public void UpdateAnimatedTiles(AnimatedTileDesc tileDesc, Texture2D animationTex)
        {
            for (int i = 0; i < tileData.Count; i++)
            {
                tileData[i].UpdateAnimatedTiles(tileDesc, animationTex);
            }
        }

        /// <summary>
        /// Editor Use: Manually add an animated tile to the map
        /// </summary>
        public void AddAnimatedTile(AnimatedTile tile)
        {
            // add the animated tile to the appropriate tile
            tileData[tile.ID].AddAnimatedTile(tile);
        }

        /// <summary>
        /// Update all tiles to use map dimensions.  Useful if map dimensions have changed since tiles where created.
        /// </summary>
        public void UpdateTileRectangles()
        {
            FindTilesetDimensions();

            foreach (Tile tile in tileData)
                tile.FindSourceRectangles();
        }

        #endregion
    }

    /*
    public class MapContentReader : ContentTypeReader<Map>
    {
        protected override Map Read(
                ContentReader input,
                Map map)
        {
            map.AssetType = input.ReadString();
            map.ID = input.ReadString();
            map.BgColor = input.ReadColor();
            map.TileSet = input.ReadString();
            map.TilesetWidth = (int)input.ReadDouble();
            map.TilesetHeight = (int)input.ReadDouble();
            map.Tile_Size = (int)input.ReadDouble();
            map.Width = (int)input.ReadDouble();
            map.Height = (int)input.ReadDouble();
            map.LightParams = input.ReadObject<LightParams>();
            map.LightList = input.ReadObject<List<PointLight>>();
            map.AnimatedTileList = input.ReadObject<List<AnimatedTileInfo>>();
            map.SpawnList = input.ReadObject<List<MapEntityInfo>>();
            map.ParticleEmitterList = input.ReadObject<List<ParticleEmitterInfo>>();
            map.PortalList = input.ReadObject<List<PortalInfo>>();
            map.ShaderRegionList = input.ReadObject<List<ShaderRegionInfo>>();
            map.TerrainList = input.ReadObject<List<TerrainInfo>>();
            map.EventList = input.ReadObject<List<MapEventInfo>>();
            map.Layers = input.ReadObject<List<List<string>>>();
            map.ForegroundLayers = input.ReadObject<List<bool>>();
            map.HazardLayer = input.ReadObject<List<string>>();
            map.CollisionLayer = input.ReadObject<List<string>>();
            map.TerrainLayer = input.ReadObject<List<string>>();

            return map;
        }
    }
    */
}