X-Git-Url: https://git.dogcows.com/gitweb?p=chaz%2Fcarfire;a=blobdiff_plain;f=CarFire%2FCarFire%2FCarFire%2FMap.cs;h=504a68ce7f9dca706131c80835b2c67c3b1fcce1;hp=ed8c39fd1957986f0014a404d44c702ed982bfe3;hb=08f41ef45f3c41ca6302150bc6d5270c8e7143db;hpb=c5daf1d9adca0c3a826dfa2ac7b6d4f8a64c84a3 diff --git a/CarFire/CarFire/CarFire/Map.cs b/CarFire/CarFire/CarFire/Map.cs index ed8c39f..504a68c 100644 --- a/CarFire/CarFire/CarFire/Map.cs +++ b/CarFire/CarFire/CarFire/Map.cs @@ -2,11 +2,12 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using Microsoft.Xna.Framework; using System.IO; -using System.Text.RegularExpressions; using System.Runtime.Serialization; using System.Diagnostics; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.Reflection; namespace CarFire { @@ -18,33 +19,601 @@ namespace CarFire /// public class Map { - #region Types + #region Public Constants - public class Data - { - public string Author; - public string Name; - public int MinNumPlayers; - public int MaxNumPlayers; - public Point Dimensions; - public string Tileset; // TODO: this should be a tilemap object - public Type Type; - public char[,] Grid; - } - - public enum Type + public const float PixelsToUnitSquares = 64.0f; + + #endregion + + + #region Public Types + + /// + /// The type of a map helps determine how the map is intended to be used. + /// + public enum Mode { None, Campaign, Battle } + /// + /// The container class for map metadata. + /// + public class Metadata + { + public string Name; + public Mode Type; + public string Author; + public string Next; + public HashSet NumPlayers = new HashSet(); + public string Tileset; + public int GridWidth; + public int GridHeight; + } + + /// + /// The container class for information about an entity defined in the map. + /// + public class RawEntity + { + public char Id; + public Point Position; + public Dictionary Attributes = new Dictionary(); + } + + #endregion + + + #region Public Properties + + /// + /// Get the name of the map. + /// + public string Name { get { return mData.Metadata.Name; } } + + /// + /// Get the type of the map. + /// + public Mode Type { get { return mData.Metadata.Type; } } + + /// + /// Get the author of the map. + /// + public string Author { get { return mData.Metadata.Author; } } + + /// + /// Get the name of the next map to load after this one. + /// + public string Next { get { return mData.Metadata.Next; } } + + /// + /// Get a set of integers containing each allowable number of players. + /// + public HashSet NumPlayers { get { return mData.Metadata.NumPlayers; } } + + /// + /// Get the width of the map, in grid units. + /// + public int Width { get { return mData.Metadata.GridWidth; } } + + /// + /// Get the height of the map, in grid units. + /// + public int Height { get { return mData.Metadata.GridHeight; } } + + /// + /// Get the name of the tileset. + /// + public string Tileset { get { return mData.Metadata.Tileset; } } + + /// + /// Get the current grid of open cells. On the grid, true means + /// the cell is open (i.e. an entity can occupy that cell), and false + /// means the cell is closed. Note that, just like Map.IsCellOpen, + /// only static scenery is considered; the grid cannot tell you + /// whether or not an entity is already occupying the cell. + /// + public bool[,] Grid { get { return mData.Grid; } } + + /// + /// Get a list of the raw entity containers loaded with the map. If you + /// want to get the actual entity objects, use Map.GetEntities and + /// Map.GetAllEntities instead. + /// + public List RawEntities { get { return mData.Entities; } } + + + /// + /// Get and set the coordinate of the grid cell that should be in + /// the center of the screen when the map is drawn. Setting this + /// will change the viewport of the map and will effect the return + /// values of Map.GetPointFromCoordinates and Map.GetRectangleFromCoordinates. + /// + public Vector2 CenterCell + { + get { return mView.CenterCell; } + set { mView.CenterCell = value; } + } + + /// + /// Get and set the tilemap with its associated texture and tile + /// character to coordinate mappings. This effects what the map looks + /// like when it is drawn. You will need to reset any map instances + /// after setting a new tilemap. You should also set a tilemap before + /// instantiating any maps. + /// + public static Tilemap Tilemap; + + /// + /// Get and set the zoom of the map view. The default zoom is + /// Map.PixelsToUnitSquares. + /// + public float Zoom + { + get { return mView.Zoom; } + set { mView.Zoom = value; } + } + + #endregion + + + #region Public Methods + + /// + /// Construct a map with the provided map data. + /// + /// The metadata. + /// The grid. + /// The entities. + public Map(Metadata metadata, char[,] grid, char defaultTile, + List entities, Point[] playerPositions) + { + mData = new Model(metadata, grid, defaultTile, entities, playerPositions); + mView = new View(mData); + } + + + /// + /// Draw a representation of the map to the screen. + /// + /// The jeewiz. + public void Draw(SpriteBatch spriteBatch) + { + mView.Draw(spriteBatch); + } + + + /// + /// Get a point in screen-space from a coordinate in gridspace. + /// + /// X-coordinate. + /// Y-coordinate. + /// Transformed point. + public Point GetPointFromCoordinates(float x, float y) + { + return mView.GetPointFromCoordinates(x, y); + } + + /// + /// Get a point in screen-space from a coordinate in gridspace. + /// + /// X,Y-coordinates. + /// Transformed point. + public Point GetPointFromCoordinates(Vector2 point) + { + return mView.GetPointFromCoordinates(point.X, point.Y); + } + + /// + /// Get a rectangle in screen-space centered around a coordinate in gridspace. + /// + /// X-coordinate. + /// Y-coordinate. + /// Transformed rectangle. + public Rectangle GetRectangleFromCoordinates(float x, float y) + { + return mView.GetRectangleFromCoordinates(x, y); + } + + /// + /// Get a rectangle in screen-space centered around a coordinate in gridspace. + /// + /// X,Y-coordinates. + /// Transformed rectangle. + public Rectangle GetRectangleFromCoordinates(Vector2 point) + { + return mView.GetRectangleFromCoordinates(point.X, point.Y); + } + + + /// + /// Determine whether or not a cell can be occupied by a game entity. + /// + /// X-coordinate. + /// Y-coordinate. + /// True if cell can be occupied, false otherwise. + public bool IsCellOpen(int x, int y) + { + return mData.IsCellOpen(x, y); + } + + /// + /// created by Brady for AI precalculations + /// + /// X-coordinate. + /// Y-coordinate. + public bool IsWall(int x, int y) + { + return mData.IsWall(x, y); + } + + /// + /// Determine whether or not a cell can be occupied by a game entity. + /// + /// X,Y-coordinates. + /// True if cell can be occupied, false otherwise. + public bool IsCellOpen(Point point) + { + return mData.IsCellOpen(point.X, point.Y); + } + + + /// + /// Get the starting position of a player. + /// + /// The number of the player (i.e. 1-4). + /// This number must be a valid player number. + /// The starting position of the player. + public Point GetStartingPositionForPlayer(int playerNumber) + { + Debug.Assert(1 <= playerNumber && playerNumber <= NumPlayers.Max()); + return mData.PlayerPositions[playerNumber]; + } + + + /// + /// Get all the entities loaded from the map file. Exceptions could be + /// thrown if there are entities without associated classes. + /// + /// The game reference to be passed to entities. + /// List of entity objects loaded. + public List GetAllEntities(Game game) + { + return mData.GetAllEntities(game); + } + + /// + /// Get the entities of a certain type loaded from the map file. Exceptions + /// could be thrown if there are entities without associated classes. + /// + /// The game reference to be passed to entities. + /// Type of the entity you want a list of. + /// List of entity objects loaded. + public List GetEntities(Game game) + { + return mData.GetEntities(game); + } + + + /// + /// Set the tile of a cell. + /// + /// X-coordinate. + /// Y-coordinate. + /// The character representing the tile. + public void SetCell(int x, int y, char tile) + { + mData.SetCell(x, y, tile); + } + + /// + /// Set the tile of a cell. + /// + /// X,Y-coordinates. + /// The character representing the tile. + public void SetCell(Point point, char tile) + { + mData.SetCell(point.X, point.Y, tile); + } + + + /// + /// Clear a cell to the default tile. + /// + /// X-coordinate. + /// Y-coordinate. + public void ClearCell(int x, int y) + { + mData.ClearCell(x, y); + } + + /// + /// Clear a cell to the default tile. + /// + /// X,Y-coordinates. + public void ClearCell(Point point) + { + mData.ClearCell(point.X, point.Y); + } + + + /// + /// Reset the map to the state it was at right after loading. + /// + public void Reset() + { + mData.Reset(); + mView.Reset(); + } + #endregion - public Map(Data data) + #region Private Types + + class Model { - Console.WriteLine("Read map " + data.Name + " of type " + data.Type + " written by " + data.Author); + public Metadata Metadata { get { return mMetadata; } } + public List Entities { get { return mEntities; } } + public Point[] PlayerPositions { get { return mPlayerPositions; } } + public bool[,] Grid { get { return mBooleanGrid; } } + + + public Model(Metadata metadata, char[,] grid, char defaultTile, + List entities, Point[] playerPositions) + { + Debug.Assert(metadata != null); + Debug.Assert(grid != null); + Debug.Assert(entities != null); + Debug.Assert(metadata.GridWidth * metadata.GridHeight == grid.Length); + Debug.Assert(Tilemap != null); + + mMetadata = metadata; + mCleanGrid = grid; + mDefaultTile = defaultTile; + mEntities = entities; + mPlayerPositions = playerPositions; + + Reset(); + +#if DEBUG + Console.WriteLine("Loaded map {0} of type {1} written by {2}.", + metadata.Name, + metadata.Type, + metadata.Author); +#endif + } + + + public void Reset() + { + mGrid = (char[,])mCleanGrid.Clone(); + + mBooleanGrid = new bool[mMetadata.GridWidth, mMetadata.GridHeight]; + for (int x = 0; x < mMetadata.GridWidth; x++) + { + for (int y = 0; y < mMetadata.GridHeight; y++) + { + mBooleanGrid[x, y] = IsCellOpen(x, y); + } + } + } + + + public bool IsCellOpen(int x, int y) + { + if (!IsOnMap(x, y)) return false; + return (Tilemap.GetTileFlags(mGrid[x, y]) & TileFlags.Open) == TileFlags.Open; + } + + //created by Brady for AI precalculations + public bool IsWall(int x, int y) + { + if (!IsOnMap(x, y)) return false; + return (Tilemap.GetTileFlags(mGrid[x, y]) & TileFlags.Wall) == TileFlags.Wall; + } + + public void SetCell(int x, int y, char tile) + { + if (IsOnMap(x, y)) + { + mGrid[x, y] = tile; + mBooleanGrid[x, y] = IsCellOpen(x, y); + } + } + + public char GetCell(int x, int y) + { + if (IsOnMap(x, y)) return mGrid[x, y]; + return mDefaultTile; + } + + public void ClearCell(int x, int y) + { + SetCell(x, y, mDefaultTile); + } + + public bool IsOnMap(int x, int y) + { + return 0 <= x && x < Metadata.GridWidth && 0 <= y && y < Metadata.GridHeight; + } + + + public List GetAllEntities(Game game) + { + List list = new List(); + + foreach (RawEntity raw in mEntities) + { + if (raw.Attributes.ContainsKey("type")) + { + string typename = raw.Attributes["type"]; + + object[] args = new object[4]; + args[0] = raw.Id; + args[1] = raw.Position; + args[2] = raw.Attributes; + args[3] = game; + + try + { + IEntity entity = (IEntity)Activator.CreateInstance(System.Type.GetType("CarFire." + typename), args); + if (entity != null) + { + entity.LoadContent(game.ContentManager); + list.Add(entity); + } + else throw new Exception(); + } +#pragma warning disable 0168 + catch (System.Exception ex) +#pragma warning restore 0168 + { + throw new Exception("Entity of type " + typename + " not loaded because an entity class can't be found."); + } + } + else + { + Console.WriteLine("Ignoring entity with identifier " + raw.Id + " since it has no type key."); + } + } + + return list; + } + + public List GetEntities(Game game) + { + System.Type type = typeof(T); + List list = new List(); + + string typename = typeof(T).Name; + foreach (RawEntity raw in mEntities) + { + if (raw.Attributes.ContainsKey("type") && typename == raw.Attributes["type"]) + { + object[] args = new object[4]; + args[0] = raw.Id; + args[1] = raw.Position; + args[2] = raw.Attributes; + args[3] = game; + + T entity = (T)Activator.CreateInstance(type, args); + if (entity != null) + { + ((IEntity)entity).LoadContent(game.ContentManager); + list.Add(entity); + } + else throw new Exception("Entity of type " + typename + " not loaded because an entity class can't be found."); + } + } + + return list; + } + + + Metadata mMetadata; + char[,] mGrid; + char[,] mCleanGrid; + bool[,] mBooleanGrid; + char mDefaultTile; + List mEntities; + Point[] mPlayerPositions; + } + + class View + { + public Vector2 CenterCell; + public float Zoom; + + + public View(Model data) + { + Debug.Assert(data != null); + mData = data; + + Reset(); + } + + + public void Reset() + { + CenterCell = Vector2.Zero; + Zoom = PixelsToUnitSquares; + } + + + public void Draw(SpriteBatch spriteBatch) + { + if (Tilemap == null) throw new Exception("Cannot draw map without first setting the tilemap."); + mViewport = spriteBatch.GraphicsDevice.Viewport; + + for (int y = 0; y < mData.Metadata.GridHeight; y++) + { + for (int x = 0; x < mData.Metadata.GridWidth; x++) + { + Tilemap.Draw(spriteBatch, mData.GetCell(x, y), GetRectangleFromCoordinates(x, y)); + } + } + } + + + public Point GetPointFromCoordinates(float x, float y) + { + Matrix transform = GetTransformation(CenterCell); + Vector2 point = Vector2.Transform(new Vector2(x, y), transform); + + return new Point((int)point.X, (int)point.Y); + } + + public Rectangle GetRectangleFromCoordinates(float x, float y) + { + Matrix transform = GetTransformation(CenterCell); + Vector2 point = Vector2.Transform(new Vector2(x, y), transform); + + return new Rectangle((int)Math.Round(point.X, 0), (int)Math.Round(point.Y, 0), (int)Math.Round(Zoom, 0), (int)Math.Round(Zoom, 0)); + } + + + + Matrix GetTransformation(Vector2 center) + { + float halfRatio = Zoom * 0.5f; + Matrix transform = Matrix.CreateTranslation(-center.X, -center.Y, 0.0f); + transform *= Matrix.CreateScale(Zoom); + transform *= Matrix.CreateTranslation(mViewport.Width * 0.5f - halfRatio, + mViewport.Height * 0.5f - halfRatio, 0.0f); + + Vector2 topLeft = Vector2.Transform(new Vector2(0.0f, 0.0f), transform); + topLeft.X = Math.Max(mViewport.X, topLeft.X); + topLeft.Y = Math.Max(mViewport.Y, topLeft.Y); + transform *= Matrix.CreateTranslation(-topLeft.X, -topLeft.Y, 0.0f); + + Vector2 bottomRight = Vector2.Transform(new Vector2((float)mData.Metadata.GridWidth, + (float)mData.Metadata.GridHeight), transform); + float right = mViewport.X + mViewport.Width; + float bottom = mViewport.Y + mViewport.Height; + bottomRight.X = Math.Min(right, bottomRight.X) - right; + bottomRight.Y = Math.Min(bottom, bottomRight.Y) - bottom; + transform *= Matrix.CreateTranslation(-bottomRight.X, -bottomRight.Y, 0.0f); + + return transform; + } + + + Model mData; + Viewport mViewport; } + + #endregion + + + #region Private Variables + + Model mData; + View mView; + + #endregion } }