/* * 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 + "]"; } }