/*
* Tiled Map Editor, (c) 2004-2006
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Adam Turk <aturk@biggeruniverse.com>
* Bjorn Lindeijer <bjorn@lindeijer.nl>
*/
package tiled.core;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Vector;
import tiled.mapeditor.Resources;
/**
* The Map class is the focal point of the <code>tiled.core</code> package.
* This class also handles notifing listeners if there is a change to any layer
* or object contained by the map.
*
* @version $Id$
*/
public class Map extends MultilayerPlane
{
/** Orthogonal. */
public static final int MDO_ORTHO = 1;
/** Isometric. */
public static final int MDO_ISO = 2;
/** Hexagonal. */
public static final int MDO_HEX = 4;
/** Shifted (used for iso and hex). */
public static final int MDO_SHIFTED = 5;
private Vector<MapLayer> specialLayers;
private Vector<TileSet> tilesets;
private LinkedList<MapObject> objects;
private int tileWidth, tileHeight;
private int orientation = MDO_ORTHO;
private final List<MapChangeListener> mapChangeListeners = new LinkedList<MapChangeListener>();
private Properties properties;
private String filename;
/**
* @param width the map width in tiles.
* @param height the map height in tiles.
*/
public Map(int width, int height) {
super(width, height);
properties = new Properties();
tilesets = new Vector<TileSet>();
specialLayers = new Vector<MapLayer>();
objects = new LinkedList<MapObject>();
}
/**
* Adds a change listener. The listener will be notified when the map
* changes in certain ways.
*
* @param listener the change listener to add
* @see MapChangeListener#mapChanged(MapChangedEvent)
*/
public void addMapChangeListener(MapChangeListener listener) {
mapChangeListeners.add(listener);
}
/**
* Removes a change listener.
* @param listener the listener to remove
*/
public void removeMapChangeListener(MapChangeListener listener) {
mapChangeListeners.remove(listener);
}
/**
* Notifies all registered map change listeners about a change.
*/
protected void fireMapChanged() {
Iterator<MapChangeListener> iterator = mapChangeListeners.iterator();
MapChangedEvent event = null;
while (iterator.hasNext()) {
if (event == null) event = new MapChangedEvent(this);
iterator.next().mapChanged(event);
}
}
/**
* Notifies all registered map change listeners about the removal of a
* tileset.
*
* @param index the index of the removed tileset
*/
protected void fireTilesetRemoved(int index) {
Iterator<MapChangeListener> iterator = mapChangeListeners.iterator();
MapChangedEvent event = null;
while (iterator.hasNext()) {
if (event == null) event = new MapChangedEvent(this);
iterator.next().tilesetRemoved(event, index);
}
}
/**
* Notifies all registered map change listeners about the addition of a
* tileset.
*
* @param tileset the new tileset
*/
protected void fireTilesetAdded(TileSet tileset) {
Iterator<MapChangeListener> iterator = mapChangeListeners.iterator();
MapChangedEvent event = null;
while (iterator.hasNext()) {
if (event == null) event = new MapChangedEvent(this);
iterator.next().tilesetAdded(event, tileset);
}
}
/**
* Notifies all registered map change listeners about the reorder of the
* tilesets.
*/
protected void fireTilesetsSwapped(int index0, int index1) {
Iterator<MapChangeListener> iterator = mapChangeListeners.iterator();
MapChangedEvent event = null;
while (iterator.hasNext()) {
if (event == null) event = new MapChangedEvent(this);
iterator.next().tilesetsSwapped(event, index0, index1);
}
}
/**
* Causes a MapChangedEvent to be fired.
*/
public void touch() {
fireMapChanged();
}
public void addLayerSpecial(MapLayer layer) {
layer.setMap(this);
specialLayers.add(layer);
fireMapChanged();
}
@Override
public MapLayer addLayer(MapLayer layer) {
layer.setMap(this);
super.addLayer(layer);
fireMapChanged();
return layer;
}
/**
* Create a new empty TileLayer with the dimensions of the map. By default,
* the new layer's name is set to "Layer [layer index]"
*
* @return The new TileLayer instance.
*/
public TileLayer addLayer() {
TileLayer layer = new TileLayer(this, bounds.width, bounds.height);
layer.setName(Resources.getString("general.layer.layer") +
" " + super.getTotalLayers());
super.addLayer(layer);
fireMapChanged();
return layer;
}
@Override
public void setLayer(int index, MapLayer layer) {
layer.setMap(this);
super.setLayer(index, layer);
fireMapChanged();
}
/**
* Create a new empty ObjectGroup. By default, the new layer's name is set
* to "ObjectGroup [layer index]"
*
* @return The new ObjectGroup instance.
*/
public MapLayer addObjectGroup() {
MapLayer layer = new ObjectGroup(this);
layer.setName(Resources.getString("general.objectgroup.objectgroup") +
" " + super.getTotalLayers());
super.addLayer(layer);
fireMapChanged();
return layer;
}
/**
* Adds a Tileset to this Map. If the set is already attached to this map,
* <code>addTileset</code> simply returns.
*
* @param tileset a tileset to add
*/
public void addTileset(TileSet tileset) {
if (tileset == null || tilesets.indexOf(tileset) > -1) {
return;
}
Tile t = tileset.getTile(0);
if (t != null) {
int tw = t.getWidth();
int th = t.getHeight();
if (tw != tileWidth) {
if (tileWidth == 0) {
tileWidth = tw;
tileHeight = th;
}
}
}
tilesets.add(tileset);
fireTilesetAdded(tileset);
}
/**
* Removes a {@link TileSet} from the map, and removes any tiles in the set
* from the map layers. A {@link MapChangedEvent} is fired when all
* processing is complete.
*
* @param tileset TileSet to remove
* @throws LayerLockedException when the tileset is in use on a locked
* layer
*/
public void removeTileset(TileSet tileset) throws LayerLockedException {
// Sanity check
final int tilesetIndex = tilesets.indexOf(tileset);
if (tilesetIndex == -1)
return;
// Go through the map and remove any instances of the tiles in the set
Iterator<Tile> tileIterator = tileset.iterator();
while (tileIterator.hasNext()) {
Tile tile = tileIterator.next();
Iterator<MapLayer> layerIterator = getLayers();
while (layerIterator.hasNext()) {
MapLayer ml = layerIterator.next();
if (ml instanceof TileLayer) {
((TileLayer) ml).removeTile(tile);
}
}
}
tilesets.remove(tileset);
fireTilesetRemoved(tilesetIndex);
}
public void addObject(MapObject o) {
objects.add(o);
}
public Iterator<MapObject> getObjects() {
return objects.iterator();
}
/**
* @return the map properties
*/
public Properties getProperties() {
return properties;
}
public void setProperties(Properties prop) {
properties = prop;
}
/**
* Calls super method, and additionally fires a {@link MapChangedEvent}.
*
* @see MultilayerPlane#removeLayer(int)
*/
@Override
public MapLayer removeLayer(int index) {
MapLayer layer = super.removeLayer(index);
fireMapChanged();
return layer;
}
public void removeLayerSpecial(MapLayer layer) {
if (specialLayers.remove(layer)) {
fireMapChanged();
}
}
public void removeAllSpecialLayers() {
specialLayers.clear();
fireMapChanged();
}
/**
* Calls super method, and additionally fires a {@link MapChangedEvent}.
*
* @see MultilayerPlane#removeAllLayers
*/
@Override
public void removeAllLayers() {
super.removeAllLayers();
fireMapChanged();
}
/**
* Calls super method, and additionally fires a {@link MapChangedEvent}.
*
* @see MultilayerPlane#setLayerVector
*/
@Override
public void setLayerVector(Vector<MapLayer> layers) {
super.setLayerVector(layers);
fireMapChanged();
}
/**
* Calls super method, and additionally fires a {@link MapChangedEvent}.
*
* @see MultilayerPlane#swapLayerUp
*/
@Override
public void swapLayerUp(int index) {
super.swapLayerUp(index);
fireMapChanged();
}
/**
* Calls super method, and additionally fires a {@link MapChangedEvent}.
*
* @see MultilayerPlane#swapLayerDown
*/
@Override
public void swapLayerDown(int index) {
super.swapLayerDown(index);
fireMapChanged();
}
/**
* Calls super method, and additionally fires a {@link MapChangedEvent}.
*
* @see MultilayerPlane#mergeLayerDown
*/
@Override
public void mergeLayerDown(int index) {
super.mergeLayerDown(index);
fireMapChanged();
}
public void setFilename(String filename) {
this.filename = filename;
}
/**
* Sets a new tile width.
*
* @param width the new tile width
*/
public void setTileWidth(int width) {
tileWidth = width;
fireMapChanged();
}
/**
* Sets a new tile height.
*
* @param height the new tile height
*/
public void setTileHeight(int height) {
tileHeight = height;
fireMapChanged();
}
/**
* Calls super method, and additionally fires a {@link MapChangedEvent}.
*
* @see MultilayerPlane#resize
*/
@Override
public void resize(int width, int height, int dx, int dy) {
super.resize(width, height, dx, dy);
fireMapChanged();
}
public void setOrientation(int orientation) {
this.orientation = orientation;
// TODO: fire mapChangedNotification about orientation change
}
public String getFilename() {
return filename;
}
public Iterator<MapLayer> getLayersSpecial() {
return specialLayers.iterator();
}
/**
* Returns a vector with the currently loaded tilesets.
*
* @return Vector
*/
public Vector<TileSet> getTilesets() {
return tilesets;
}
/**
* Get the tile set that matches the given global tile id, only to be used
* when loading a map.
*
* @param gid a global tile id
* @return the tileset containing the tile with the given global tile id,
* or <code>null</code> when no such tileset exists
*/
public TileSet findTileSetForTileGID(int gid) {
TileSet has = null;
for (TileSet tileset : tilesets) {
if (tileset.getFirstGid() <= gid) {
has = tileset;
}
}
return has;
}
/**
* Returns width of map in tiles.
*
* @return int
*/
public int getWidth() {
return bounds.width;
}
/**
* Returns height of map in tiles.
*
* @return int
*/
public int getHeight() {
return bounds.height;
}
/**
* Returns default tile width for this map.
*
* @return the default tile width
*/
public int getTileWidth() {
return tileWidth;
}
/**
* Returns default tile height for this map.
*
* @return the default tile height
*/
public int getTileHeight() {
return tileHeight;
}
/**
* Returns wether the given tile coordinates fall within the map
* boundaries.
*
* @param x The tile-space x-coordinate
* @param y The tile-space y-coordinate
* @return <code>true</code> if the point is within the map boundaries,
* <code>false</code> otherwise
*/
public boolean contains(int x, int y) {
return x >= 0 && y >= 0 && x < bounds.width && y < bounds.height;
}
/**
* Returns the maximum tile height. This is the height of the highest tile
* in all tilesets or the tile height used by this map if it's smaller.
*
* @return int The maximum tile height
*/
public int getTileHeightMax() {
int maxHeight = tileHeight;
for (TileSet tileset : tilesets) {
int height = tileset.getTileHeight();
if (height > maxHeight) {
maxHeight = height;
}
}
return maxHeight;
}
/**
* Swaps the tile sets at the given indices.
*/
public void swapTileSets(int index0, int index1) {
if (index0 == index1) return;
TileSet set = tilesets.get(index0);
tilesets.set(index0, tilesets.get(index1));
tilesets.set(index1, set);
if (index0 > index1) {
int temp = index1;
index1 = index0;
index0 = temp;
}
fireTilesetsSwapped(index0, index1);
}
/**
* Returns the sum of the size of each tile set.
*
* @return
*/
/*
public int getTotalTiles() {
int totalTiles = 0;
Iterator itr = tilesets.iterator();
while (itr.hasNext()) {
TileSet cur = (TileSet)itr.next();
totalTiles += cur.getTotalTiles();
}
return totalTiles;
}
*/
/**
* Returns the orientation of this map. Orientation will be one of
* {@link Map#MDO_ISO}, {@link Map#MDO_ORTHO}, {@link Map#MDO_HEX},
* and {@link Map#MDO_SHIFTED}.
*
* @return The orientation from the enumerated set
*/
public int getOrientation() {
return orientation;
}
/**
* Returns string describing the map. The form is <code>Map[width x height
* x layers][tileWidth x tileHeight]</code>, for example <code>
* Map[64x64x2][24x24]</code>.
*
* @return string describing map
*/
@Override
public String toString() {
return "Map[" + bounds.width + "x" + bounds.height + "x" +
getTotalLayers() + "][" + tileWidth + "x" +
tileHeight + "]";
}
}