using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using V3.Camera;
using V3.Objects;
namespace V3.Map
{
///
/// A drawable map layer usually created from a Tiled map file.
///
public abstract class AbstractLayer
{
private const int CellHeight = Constants.CellHeight;
private const int CellWidth = Constants.CellWidth;
private readonly int mTileWidth;
private readonly int mTileHeight;
private readonly int mMapWidth;
private readonly int mMapHeight;
private readonly List mTextureObjects = new List();
private readonly int[,] mTileArray;
private readonly SortedList mTilesets;
protected AbstractLayer(int tileWidth,
int tileHeight,
int mapWidth,
int mapHeight,
int[,] tileArray,
SortedList tilesets)
{
mTileWidth = tileWidth;
mTileHeight = tileHeight;
mMapWidth = mapWidth;
mMapHeight = mapHeight;
mTilesets = tilesets;
if (tileArray.Length == mapWidth * mapHeight)
{
mTileArray = tileArray;
}
else
{
throw new Exception("Error constructing map layer. Map size does not fit the map description.");
}
}
///
/// Create the map objects according to the given map array.
///
public void CreateObjects()
{
var firstgridList = mTilesets.Keys;
for (int i = 0; i < mMapHeight; i++)
{
int horizontalOffset = (i % 2 == 0) ? (-mTileWidth / 2) : 0;
for (int j = 0; j < mMapWidth; j++)
{
int tileId = mTileArray[i, j];
// Checks which tileset needs to be used for the specific tile ID at position [i, j]
for (int k = firstgridList.Count - 1; k >= 0; k--)
{
if (tileId == 0)
{
// This does generally nothing. But you can overwrite GenerateNullObject() for other behaviour.
TextureObject objectToInsert = GenerateNullObject();
if (objectToInsert != null)
{
mTextureObjects.Add(objectToInsert);
}
break;
}
else if (tileId >= firstgridList[k])
{
Tileset tileset = mTilesets.Values[k];
int firstgrid = firstgridList[k];
Point position = SelectPosition(j, i, horizontalOffset);
Point textureSize = new Point(tileset.TileWidth, tileset.TileHeight);
Point destination = SelectDestination(j, i, horizontalOffset, tileset.OffsetX, tileset.TileHeight, tileset.OffsetY);
Point source = SelectSource(tileId, firstgrid, tileset.TileWidth, tileset.TileHeight, tileset.Columns);
IGameObject objectToInsert;
if (tileset.Name == "houses_rear" || tileset.Name == "houses_front")
{
if (source.Y < textureSize.Y * 2)
{
int initialDamage = 0;
if (source.X / textureSize.X == 1)
{
initialDamage = 50;
}
else if (source.X / textureSize.X == 2)
{
initialDamage = 80;
}
IBuilding building = new Woodhouse(position.ToVector2(), new Rectangle(destination, textureSize), tileset.Name, source.Y % 384 == 0 ? BuildingFace.SW : BuildingFace.NO);
building.TakeDamage(initialDamage);
objectToInsert = building;
}
else
{
int initialDamage = 0;
if (source.X / textureSize.X == 1)
{
initialDamage = 60;
}
else if (source.X / textureSize.X == 2)
{
initialDamage = 100;
}
IBuilding building = new Forge(position.ToVector2(), new Rectangle(destination, textureSize), tileset.Name, source.Y % 384 == 0 ? BuildingFace.SW : BuildingFace.NO);
building.TakeDamage(initialDamage);
objectToInsert = building;
}
}
else if (tileset.Name == "castle")
{
IBuilding building = new Objects.Castle(position.ToVector2(), new Rectangle(destination, textureSize), tileset.Name, BuildingFace.SW);
objectToInsert = building;
}
else
{
objectToInsert = new TextureObject(position,
destination,
textureSize,
source,
tileset.Name);
}
mTextureObjects.Add(objectToInsert);
break;
}
}
}
}
}
protected virtual TextureObject GenerateNullObject()
{
return null;
}
///
/// Loads the image files needed for drawing the tilesets.
///
/// Content manager used for loading the ressources.
public void LoadContent(ContentManager contentManager)
{
mTextureObjects.ForEach(o => o.LoadContent(contentManager));
}
///
/// Draws only the parts of the map which are visible. More efficient than the other Draw-Method.
/// Not very robust and maybe does not work correctly most layers.
/// This is because of gaps in the list of game objects.
///
/// Sprite batch used.
/// Needed to tell which objects of the map are looked upon.
public void Draw(SpriteBatch spriteBatch, ICamera camera)
{
int tilesHorizontal = camera.ScreenSize.X / mTileWidth;
int tilesVertical = camera.ScreenSize.Y * 2 / mTileHeight;
int horizontalStart = camera.ScreenRectangle.X / mTileWidth;
int verticalStart = camera.ScreenRectangle.Y * 2 / mTileHeight;
/*
for (int i = 0; i < tilesVertical; i++)
{
for (int j = 0; j < tilesHorizontal; j++)
{
mTextureObjects[horizontalStart + j].Draw(spriteBatch);
}
}
*/
for (int j = 0; j < tilesVertical + 2; j++)
{
for (int i = 0; i < tilesHorizontal + 2; i++)
{
int index = i + horizontalStart + (j + verticalStart) * mMapWidth;
if (index < mTextureObjects.Count)
{
mTextureObjects[index].Draw(spriteBatch);
}
}
}
}
///
/// Extract a collision grid from the map layer. Used in pathfinding.
///
/// A two dimensional boolean collision grid.
public bool[,] ExtractCollisions()
{
int gridHeight = (mMapHeight - 1) * mTileHeight/ CellHeight / 2;
int gridWidth = (mMapWidth - 1) * mTileWidth / CellWidth;
bool[,] collisionGrid = new bool[gridHeight, gridWidth];
var firstgridList = mTilesets.Keys;
for (int i = 0; i < mMapHeight; i++)
{
for (int j = 0; j < mMapWidth; j++)
{
int tileId = mTileArray[i, j];
for (int k = firstgridList.Count - 1; k >= 0; k--)
{
if (tileId >= firstgridList[k])
{
Tileset tileset = mTilesets.Values[k];
int firstgrid = firstgridList[k];
tileId -= firstgrid;
bool[,] collisionData;
// Is there even collision data for the specific tile ID?
if (tileset.TileCollisions.TryGetValue(tileId, out collisionData))
{
int cellOffset = (i % 2 == 0 ? -mTileWidth / 2 : 0) / CellWidth;
int cellsHorizontal = mTileWidth / CellWidth;
int cellsVertical = mTileHeight / CellHeight;
int iStart = (i - 1) * cellsVertical / 2 + cellsVertical - tileset.CollisionHeight + tileset.OffsetY / CellHeight;
int jStart = j * cellsHorizontal + cellOffset + tileset.OffsetX / CellWidth;
for (int iData = 0; iData < tileset.CollisionHeight; iData++)
{
for (int jData = 0; jData < tileset.CollisionWidth; jData++)
{
// Do we even need to update collisionGrid?
if (iStart + iData >= 0 && iStart + iData < gridHeight && jStart + jData >= 0 && jStart + jData < gridWidth &&
collisionData[iData, jData] && !collisionGrid[iStart + iData, jStart + jData])
{
collisionGrid[iStart + iData, jStart + jData] = collisionData[iData, jData];
}
}
}
}
break;
}
}
}
}
return collisionGrid;
}
public List ExtractObjects()
{
return mTextureObjects;
}
private Point SelectDestination(int x, int y, int xOffset, int tileXOffset, int tileHeight, int tileYOffset)
{
return new Point(x * mTileWidth + xOffset + tileXOffset,
(y - 1) * (mTileHeight / 2) - tileHeight + mTileHeight + tileYOffset);
}
private Point SelectSource(int tileId, int firstgrid, int tileWidth, int tileHeight, int tilesPerRow)
{
return new Point((tileId - firstgrid) % tilesPerRow * tileWidth, (tileId - firstgrid) / tilesPerRow * tileHeight);
}
private Point SelectPosition(int x, int y, int xOffset)
{
return new Point(x * mTileWidth + xOffset + mTileWidth / 2, y * (mTileHeight / 2));
}
}
}