package org.newdawn.slick.tiled; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.Properties; import java.util.zip.GZIPInputStream; import org.newdawn.slick.SlickException; import org.newdawn.slick.util.Log; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * A layer of tiles on the map * * @author kevin */ public class Layer { /** The code used to decode Base64 encoding */ private static byte[] baseCodes = new byte[256]; /** * Static initialiser for the codes created against Base64 */ static { for (int i = 0; i < 256; i++) baseCodes[i] = -1; for (int i = 'A'; i <= 'Z'; i++) baseCodes[i] = (byte) (i - 'A'); for (int i = 'a'; i <= 'z'; i++) baseCodes[i] = (byte) (26 + i - 'a'); for (int i = '0'; i <= '9'; i++) baseCodes[i] = (byte) (52 + i - '0'); baseCodes['+'] = 62; baseCodes['/'] = 63; } /** The map this layer belongs to */ private final TiledMap map; /** The index of this layer */ public int index; /** The name of this layer - read from the XML */ public String name; /** The tile data representing this data, index 0 = tileset, index 1 = tile id */ public int[][][] data; /** The width of this layer */ public int width; /** The height of this layer */ public int height; /** the properties of this layer */ public Properties props; /** * Create a new layer based on the XML definition * * @param element The XML element describing the layer * @param map The map this layer is part of * @throws SlickException Indicates a failure to parse the XML layer */ public Layer(TiledMap map, Element element) throws SlickException { this.map = map; name = element.getAttribute("name"); width = Integer.parseInt(element.getAttribute("width")); height = Integer.parseInt(element.getAttribute("height")); data = new int[width][height][3]; // now read the layer properties Element propsElement = (Element) element.getElementsByTagName("properties").item(0); if (propsElement != null) { NodeList properties = propsElement.getElementsByTagName("property"); if (properties != null) { props = new Properties(); for (int p = 0; p < properties.getLength();p++) { Element propElement = (Element) properties.item(p); String name = propElement.getAttribute("name"); String value = propElement.getAttribute("value"); props.setProperty(name, value); } } } Element dataNode = (Element) element.getElementsByTagName("data").item(0); String encoding = dataNode.getAttribute("encoding"); String compression = dataNode.getAttribute("compression"); if (encoding.equals("base64") && compression.equals("gzip")) { try { Node cdata = dataNode.getFirstChild(); char[] enc = cdata.getNodeValue().trim().toCharArray(); byte[] dec = decodeBase64(enc); GZIPInputStream is = new GZIPInputStream(new ByteArrayInputStream(dec)); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int tileId = 0; tileId |= is.read(); tileId |= is.read() << 8; tileId |= is.read() << 16; tileId |= is.read() << 24; if (tileId == 0) { data[x][y][0] = -1; data[x][y][1] = 0; data[x][y][2] = 0; } else { TileSet set = map.findTileSet(tileId); if (set != null) { data[x][y][0] = set.index; data[x][y][1] = tileId - set.firstGID; } data[x][y][2] = tileId; } } } } catch (IOException e) { Log.error(e); throw new SlickException("Unable to decode base 64 block"); } } else { throw new SlickException("Unsupport tiled map type: "+encoding+","+compression+" (only gzip base64 supported)"); } } /** * Get the gloal ID of the tile at the specified location in * this layer * * @param x The x coorindate of the tile * @param y The y coorindate of the tile * @return The global ID of the tile */ public int getTileID(int x, int y) { return data[x][y][2]; } /** * Set the global tile ID at a specified location * * @param x The x location to set * @param y The y location to set * @param tile The tile value to set */ public void setTileID(int x, int y, int tile) { if (tile == 0) { data[x][y][0] = -1; data[x][y][1] = 0; data[x][y][2] = 0; } else { TileSet set = map.findTileSet(tile); data[x][y][0] = set.index; data[x][y][1] = tile - set.firstGID; data[x][y][2] = tile; } } /** * Render a section of this layer * * @param x * The x location to render at * @param y * The y location to render at * @param sx * The x tile location to start rendering * @param sy * The y tile location to start rendering * @param width The number of tiles across to render * @param ty The line of tiles to render * @param lineByLine * True if we should render line by line, i.e. giving us a * chance to render something else between lines * @param mapTileWidth the tile width specified in the map file * @param mapTileHeight the tile height specified in the map file */ public void render(int x,int y,int sx,int sy,int width, int ty,boolean lineByLine, int mapTileWidth, int mapTileHeight) { for (int tileset=0;tileset<map.getTileSetCount();tileset++) { TileSet set = null; for (int tx=0;tx<width;tx++) { if ((sx+tx < 0) || (sy+ty < 0)) { continue; } if ((sx+tx >= this.width) || (sy+ty >= this.height)) { continue; } if (data[sx+tx][sy+ty][0] == tileset) { if (set == null) { set = map.getTileSet(tileset); set.tiles.startUse(); } int sheetX = set.getTileX(data[sx+tx][sy+ty][1]); int sheetY = set.getTileY(data[sx+tx][sy+ty][1]); int tileOffsetY = set.tileHeight - mapTileHeight; // set.tiles.renderInUse(x+(tx*set.tileWidth), y+(ty*set.tileHeight), sheetX, sheetY); set.tiles.renderInUse(x+(tx*mapTileWidth), y+(ty*mapTileHeight)-tileOffsetY, sheetX, sheetY); } } if (lineByLine) { if (set != null) { set.tiles.endUse(); set = null; } map.renderedLine(ty, ty+sy, index); } if (set != null) { set.tiles.endUse(); } } } /** * Decode a Base64 string as encoded by TilED * * @param data The string of character to decode * @return The byte array represented by character encoding */ private byte[] decodeBase64(char[] data) { int temp = data.length; for (int ix = 0; ix < data.length; ix++) { if ((data[ix] > 255) || baseCodes[data[ix]] < 0) { --temp; } } int len = (temp / 4) * 3; if ((temp % 4) == 3) len += 2; if ((temp % 4) == 2) len += 1; byte[] out = new byte[len]; int shift = 0; int accum = 0; int index = 0; for (int ix = 0; ix < data.length; ix++) { int value = (data[ix] > 255) ? -1 : baseCodes[data[ix]]; if (value >= 0) { accum <<= 6; shift += 6; accum |= value; if (shift >= 8) { shift -= 8; out[index++] = (byte) ((accum >> shift) & 0xff); } } } if (index != out.length) { throw new RuntimeException( "Data length appears to be wrong (wrote " + index + " should be " + out.length + ")"); } return out; } }