using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace CarFire
{
///
/// A map object represents the map or virtual world where players and other
/// game entities exist. The map consists of a grid where each grid space can
/// contain static scenery and/or game entities which can move and interact
/// with other game entities.
///
public class Map
{
// DEBUG: Tilesets not implemented at all.
public static Texture2D DefaultTile;
#region Public Constants
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 Type
{
None,
Campaign,
Battle
}
///
/// The container class for map metadata.
///
public class Metadata
{
public string Name;
public Type Type;
public string Author;
public HashSet NumPlayers = new HashSet();
public string Tileset;
public int GridWidth;
public int GridHeight;
}
#endregion
#region Public Attributes
///
/// Get the name of the map.
///
public string Name { get { return mData.Metadata.Name; } }
///
/// Get the type of the map.
///
//public Type Type { get { return mData.mMetadata.Type; } }
///
/// Get the author of the map.
///
public string Author { get { return mData.Metadata.Author; } }
///
/// 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; } }
// TODO: This should return whatever object we end up using for tilesets.
public string Tileset { get { return mData.Metadata.Tileset; } }
public Vector2 CenterCell
{
get { return mView.CenterCell; }
set { mView.CenterCell = value; }
}
#endregion
#region Public Methods
///
/// Construct a map with the provided map data.
///
/// Map data.
public Map(Metadata metadata, char[,] grid, Dictionary> entities)
{
mData = new Modal(metadata, grid, entities);
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);
}
///
/// 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 entities loaded from the map file.
///
/// Dictionary of entities. The keys are the entity
/// identifiers and the value is a dictionary of key-value pairs
/// associated with that entity.
public Dictionary> GetEntities()
{
return mData.Entities;
}
#endregion
#region Private Types
class Modal
{
Metadata mMetadata;
char[,] mGrid;
Dictionary> mEntities;
public Modal(Metadata metadata, char[,] grid, Dictionary> entities)
{
Debug.Assert(metadata != null);
Debug.Assert(grid != null);
Debug.Assert(entities != null);
Debug.Assert(metadata.GridWidth * metadata.GridHeight == grid.Length);
mMetadata = metadata;
mGrid = grid;
mEntities = entities;
#if DEBUG
Console.WriteLine("Loaded map {0} of type {1} written by {2}.",
metadata.Name,
metadata.Type,
metadata.Author);
#endif
}
public Metadata Metadata { get { return mMetadata; } }
public Dictionary> Entities { get { return mEntities; } }
public bool IsCellOpen(int x, int y)
{
// TODO: Still need to define characters for types of scenery.
return mGrid[x, y] == ' ';
}
}
class View
{
Modal mData;
public Vector2 CenterCell;
Viewport mViewport;
public View(Modal data)
{
Debug.Assert(data != null);
mData = data;
}
public void Draw(SpriteBatch spriteBatch)
{
mViewport = spriteBatch.GraphicsDevice.Viewport;
// TODO: There is no culling yet, but it runs so fast that it probably won't ever need it.
for (int y = 0; y < mData.Metadata.GridHeight; y++)
{
for (int x = 0; x < mData.Metadata.GridWidth; x++)
{
if (mData.IsCellOpen(x, y))
{
spriteBatch.Draw(Map.DefaultTile, GetRectangleFromCoordinates(x, y), Color.White);
}
else
{
spriteBatch.Draw(Map.DefaultTile, GetRectangleFromCoordinates(x, y), Color.DarkBlue);
}
}
}
}
///
/// Get a matrix to transform a point from grid-space to screen coordinates. This
/// method uses the viewport to bound the edges of the map such that the camera
/// will not show anything outside of the grid.
///
/// The point to put in the center.
/// The transformation matrix.
Matrix GetTransformation(Vector2 center)
{
float halfRatio = PixelsToUnitSquares * 0.5f;
Matrix transform = Matrix.CreateTranslation(-center.X, -center.Y, 0.0f);
transform *= Matrix.CreateScale(PixelsToUnitSquares);
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;
}
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)point.X, (int)point.Y, (int)PixelsToUnitSquares, (int)PixelsToUnitSquares);
}
}
#endregion
#region Private Variables
Modal mData;
View mView;
#endregion
}
}