package org.newdawn.slick.tiled; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.Properties; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.newdawn.slick.Image; import org.newdawn.slick.SlickException; import org.newdawn.slick.util.Log; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * An extended version of the TiledMap class, with more functionality. A merge * of Liam (liamzebedee) Edwards-Playne's existing library, TiledMapPlus * * @author liamzebedee */ /* * TODO for supporting TiLeD 0.8 * ? Added support for specifying a tile drawing offset (sponsored by Clint Bellanger) */ public class TiledMapPlus extends TiledMap { private HashMap<String, Integer> objectGroupNameToOffset = new HashMap<String, Integer>(); private HashMap<String, Integer> layerNameToIDMap = new HashMap<String, Integer>(); private HashMap<String, Integer> tilesetNameToIDMap = new HashMap<String, Integer>(); /** * Create a new tile map based on a given TMX file * * @param ref * The location of the tile map to load * @throws SlickException * Indicates a failure to load the tilemap */ public TiledMapPlus(String ref) throws SlickException { this(ref, true); } /** * Create a new tile map based on a given TMX file * * @param ref * The location of the tile map to load * @param loadTileSets * True if we want to load tilesets - including their image data * @throws SlickException * Indicates a failure to load the tilemap */ public TiledMapPlus(String ref, boolean loadTileSets) throws SlickException { super(ref, loadTileSets); processNameToObjectMap(); processLayerMap(); processTilesetMap(); } /** * Create a new tile map based on a given TMX file * * @param ref * The location of the tile map to load * @param tileSetsLocation * The location where we can find the tileset images and other * resources * @throws SlickException * Indicates a failure to load the tilemap */ public TiledMapPlus(String ref, String tileSetsLocation) throws SlickException { super(ref, tileSetsLocation); processNameToObjectMap(); processLayerMap(); processTilesetMap(); } /** * Load a tile map from an arbitary input stream * * @param in * The input stream to load from * @throws SlickException * Indicates a failure to load the tilemap */ public TiledMapPlus(InputStream in) throws SlickException { super(in); processNameToObjectMap(); processLayerMap(); processTilesetMap(); } /** * Load a tile map from an arbitary input stream * * @param in * The input stream to load from * @param tileSetsLocation * The location at which we can find tileset images * @throws SlickException * Indicates a failure to load the tilemap */ public TiledMapPlus(InputStream in, String tileSetsLocation) throws SlickException { super(in, tileSetsLocation); processNameToObjectMap(); processLayerMap(); processTilesetMap(); } /** * Populates the objectGroupName to objectGroupOffset map and the * groupObjectName to groupObjectOffset map * * @author liamzebedee */ private void processNameToObjectMap() { for (int i = 0; i < this.getObjectGroupCount(); i++) { ObjectGroup g = this.objectGroups.get(i); this.objectGroupNameToOffset.put(g.name, i); HashMap<String, Integer> nameToObjectMap = new HashMap<String, Integer>(); for (int ib = 0; ib < this.getObjectCount(i); ib++) { nameToObjectMap.put(this.getObjectName(i, ib), ib); } g.setObjectNameMapping(nameToObjectMap); } } /** * Populates the tileSet name to offset map * * @author liamzebedee */ private void processLayerMap() { for (int l = 0; l < layers.size(); l++) { Layer layer = layers.get(l); this.layerNameToIDMap.put(layer.name, l); } } /** * Populates the tileSet name to offset map * * @author liamzebedee */ private void processTilesetMap() { for (int t = 0; t < this.getTileSetCount(); t++) { TileSet tileSet = this.getTileSet(t); this.tilesetNameToIDMap.put(tileSet.name, t); } } /** * Gets a layer by its name * * @author liamzebedee * @param layerName * The name of the layer to get */ public Layer getLayer(String layerName) { int layerID = this.layerNameToIDMap.get(layerName); return this.layers.get(layerID); } /** * Gets an ObjectGroup * * @author liamzebedee * @param groupName * The name of the group */ public ObjectGroup getObjectGroup(String groupName) { return this.objectGroups.get(this.objectGroupNameToOffset .get(groupName)); } /** * Gets all ObjectGroup's * * @author liamzebedee */ public ArrayList<ObjectGroup> getObjectGroups() { return this.objectGroups; } /** * Get all tiles from all layers that are part of a specific tileset * * @author liamzebedee * @param tilesetName * The name of the tileset that the tiles are part of */ public ArrayList<Tile> getAllTilesFromAllLayers(String tilesetName) { ArrayList<Tile> tiles = new ArrayList<Tile>(); int tilesetID = this.tilesetNameToIDMap.get(tilesetName); for (int x = 0; x < this.getWidth(); x++) { for (int y = 0; y < this.getHeight(); y++) { for (int l = 0; l < this.getLayerCount(); l++) { Layer layer = this.layers.get(l); if (layer.data[x][y][0] == tilesetID) { Tile t = new Tile(x, y, layer.name, layer.data[x][y][2], tilesetName); tiles.add(t); } } } } return tiles; } /** * Writes the current TiledMap to a stream * <br> Map is written using GZIP Base64 encoding in TiledMap V0.8 * * @author liamzebedee * @param stream * The stream in which the TiledMap is to be written to * @throws SlickException */ public void write(OutputStream o) throws SlickException { try { DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); Document doc = docBuilder.newDocument(); Element map = doc.createElement("map"); map.setAttribute("version", "1.0"); if(this.orientation == TiledMap.ORTHOGONAL) map.setAttribute("orientation", "orthogonal"); else if(this.orientation == TiledMap.ISOMETRIC) map.setAttribute("orientation", "isometric"); map.setAttribute("tilewidth", "" + this.tileWidth); map.setAttribute("tileheight", "" + this.tileHeight); map.setAttribute("width", "" + this.width); map.setAttribute("height", "" + this.height); doc.appendChild(map); for (int i = 0; i < this.tileSets.size(); i++) { // Loop through all tilesets TileSet tilesetData = this.tileSets.get(i); Element tileset = doc.createElement("tileset"); tileset.setAttribute("firstgid", "" + tilesetData.firstGID); tileset.setAttribute("name", tilesetData.name); tileset.setAttribute("tilewidth", "" + tilesetData.tileWidth); tileset.setAttribute("tileheight", "" + tilesetData.tileHeight); tileset.setAttribute("spacing", "" + tilesetData.tileSpacing); tileset.setAttribute("margin", "" + tilesetData.tileMargin); Element image = doc.createElement("image"); String imagePath = tilesetData.imageref.replaceFirst( this.getTilesLocation() + "/", ""); image.setAttribute("source", imagePath); image.setAttribute("width", "" + tilesetData.tiles.getWidth()); image.setAttribute("height", "" + tilesetData.tiles.getHeight()); tileset.appendChild(image); int tileCount = tilesetData.tiles.getHorizontalCount() * tilesetData.tiles.getVerticalCount(); Element tilesetProperties = doc.createElement("properties"); Properties tilesetPropertiesData = tilesetData.tilesetProperties; if (tilesetProperties != null) { Enumeration propertyEnum = tilesetPropertiesData.propertyNames(); while (propertyEnum.hasMoreElements()) { String key = (String) propertyEnum.nextElement(); Element tileProperty = doc.createElement("property"); tileProperty.setAttribute("name", key); tileProperty.setAttribute("value", tilesetPropertiesData.getProperty(key)); tilesetProperties.appendChild(tileProperty); } tileset.appendChild(tilesetProperties); } if (tileCount == 1) { tileCount++; } for (int tileI = 0; tileI < tileCount; tileI++) { Properties tileProperties = tilesetData .getProperties(tileI); if (tileProperties != null) { Element tile = doc.createElement("tile"); int tileID = tileI - tilesetData.firstGID; tile.setAttribute("id", "" + tileID); Element tileProps = doc.createElement("properties"); Enumeration propertyEnum = tilesetData.getProperties( tileI).propertyNames(); while (propertyEnum.hasMoreElements()) { String key = (String) propertyEnum.nextElement(); Element tileProperty = doc .createElement("property"); tileProperty.setAttribute("name", key); tileProperty.setAttribute("value", tileProperties.getProperty(key)); tileProps.appendChild(tileProperty); } tile.appendChild(tileProps); tileset.appendChild(tile); } } map.appendChild(tileset); } for (int i = 0; i < this.layers.size(); i++) { Element layer = doc.createElement("layer"); Layer layerData = layers.get(i); layer.setAttribute("name", layerData.name); layer.setAttribute("width", "" + layerData.width); layer.setAttribute("height", "" + layerData.height); layer.setAttribute("opacity", "" + layerData.opacity); if(layerData.visible) layer.setAttribute("visible", "1"); else layer.setAttribute("visible", "0"); Element data = doc.createElement("data"); ByteArrayOutputStream os = new ByteArrayOutputStream(); for (int tileY = 0; tileY < layerData.height; tileY++) { for (int tileX = 0; tileX < layerData.width; tileX++) { int tileGID = layerData.data[tileX][tileY][2]; os.write(tileGID); os.write(tileGID << 8); os.write(tileGID << 16); os.write(tileGID << 24); } } os.flush(); String compressedData = Base64.encodeBytes(os.toByteArray(), Base64.DONT_BREAK_LINES | Base64.GZIP | Base64.ENCODE); data.appendChild(doc.createTextNode(compressedData)); data.setAttribute("encoding", "base64"); data.setAttribute("compression", "gzip"); layer.appendChild(data); map.appendChild(layer); } for (int objectGroupI = 0; objectGroupI < this.objectGroups.size(); objectGroupI++) { Element objectGroup = doc.createElement("objectgroup"); ObjectGroup objectGroupData = objectGroups.get(objectGroupI); objectGroup.setAttribute("color", "white"); // It doesn't appear we use a color value, // but its in the format so... objectGroup.setAttribute("name", objectGroupData.name); objectGroup.setAttribute("width", "" + objectGroupData.width); objectGroup.setAttribute("height", "" + objectGroupData.height); objectGroup.setAttribute("opacity", "" + objectGroupData.opacity); if(objectGroupData.visible) objectGroup.setAttribute("visible", "1"); else objectGroup.setAttribute("visible", "0"); objectGroup.setAttribute("color", "#"+ Float.toHexString(objectGroupData.color.r) + Float.toHexString(objectGroupData.color.g) + Float.toHexString(objectGroupData.color.b)); for (int groupObjectI = 0; groupObjectI < objectGroupData.objects .size(); groupObjectI++) { Element object = doc.createElement("object"); GroupObject groupObject = objectGroupData.objects .get(groupObjectI); object.setAttribute("x", "" + groupObject.x); object.setAttribute("y", "" + groupObject.y); switch(groupObject.objectType) { case IMAGE: object.setAttribute("gid", "" + groupObject.gid); break; case RECTANGLE: object.setAttribute("name", groupObject.name); object.setAttribute("type", groupObject.type); object.setAttribute("width", "" + groupObject.width); object.setAttribute("height", "" + groupObject.height); break; case POLYGON: Element polygon = doc.createElement("polygon"); String polygonPoints = ""; for(int polygonPointIndex = 0; polygonPointIndex < groupObject.points.getPointCount() - 1; polygonPointIndex++) { polygonPoints += groupObject.points.getPoint(polygonPointIndex)[0] + "," + groupObject.points.getPoint(polygonPointIndex)[1] + " "; } polygonPoints.trim(); polygon.setAttribute("points", polygonPoints); break; case POLYLINE: Element polyline = doc.createElement("polyline"); String polylinePoints = ""; for(int polyLinePointIndex = 0; polyLinePointIndex < groupObject.points.getPointCount() - 1; polyLinePointIndex++) { polylinePoints += groupObject.points.getPoint(polyLinePointIndex)[0] + "," + groupObject.points.getPoint(polyLinePointIndex)[1] + " "; } polylinePoints.trim(); polyline.setAttribute("points", polylinePoints); break; } if (groupObject.props != null) { Element objectProps = doc.createElement("properties"); Enumeration propertyEnum = groupObject.props .propertyNames(); while (propertyEnum.hasMoreElements()) { String key = (String) propertyEnum.nextElement(); Element objectProperty = doc .createElement("property"); objectProperty.setAttribute("name", key); objectProperty.setAttribute("value", groupObject.props.getProperty(key)); objectProps.appendChild(objectProperty); } object.appendChild(objectProps); } objectGroup.appendChild(object); } map.appendChild(objectGroup); } TransformerFactory transformerFactory = TransformerFactory .newInstance(); Transformer transformer = transformerFactory.newTransformer(); DOMSource source = new DOMSource(doc); StreamResult result = new StreamResult(o); transformer.transform(source, result); } catch (Exception e) { Log.error(e); throw new SlickException("Failed to write tiledmap", e); } } /** * Gets an arrayList of all layers in the TiledMap * * @author liamzebedee */ public ArrayList<Layer> getLayers() { return this.layers; } /** * Gets an arrayList of all tilesets in the TiledMap * * @author liamzebedee */ public ArrayList<TileSet> getTilesets() { return this.tileSets; } /** * Gets the visible tile's image at the co-ordinates * * @author liamzebedee * @param x * The x co-ordinate of the tile * @param y * The y co-ordinate of the tile * @return The visible tile at this location * @throws SlickException */ public Image getVisibleTile(int x, int y) throws SlickException { Image visibleTileImage = null; for (int l = this.getLayerCount() - 1; l > -1; l--) { if (visibleTileImage == null) { visibleTileImage = this.getTileImage(x, y, l); continue; } } if (visibleTileImage == null) { throw new SlickException("Tile doesn't have a tileset!"); } return visibleTileImage; } /** * Gets the ID of a tileset from its name * * @author liamzebedee * @param tilesetName * The name of the tileset to get the id of */ public int getTilesetID(String tilesetName) { int tilesetID = this.tilesetNameToIDMap.get(tilesetName); return tilesetID; } /** * Gets the ID of a layer from its name * * @author liamzebedee * @param layerName * The name of the layer to get the id of */ public int getLayerID(String layerName) { int layerID = this.layerNameToIDMap.get(layerName); return layerID; } }