mirror of
https://github.com/OMGeeky/ATCS.git
synced 2025-12-26 23:57:25 +01:00
Added support for the new "colorfilter" map property. Due to Java2D having no correct color filter support, only the "blackXX" values can be previwed. "bw" and "invert" cannot, the performance cost was simply way too high.
969 lines
36 KiB
Java
969 lines
36 KiB
Java
/*
|
|
* Copyright 2004-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
|
|
* Copyright 2004-2006, Adam Turk <aturk@biggeruniverse.com>
|
|
*
|
|
* This file is part of libtiled-java.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
* EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
|
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
package tiled.io;
|
|
|
|
import java.awt.Color;
|
|
import java.awt.Image;
|
|
import java.awt.Rectangle;
|
|
import java.awt.image.BufferedImage;
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.File;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.Reader;
|
|
import java.io.StringReader;
|
|
import java.lang.reflect.Constructor;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.lang.reflect.Method;
|
|
import java.net.MalformedURLException;
|
|
import java.net.URL;
|
|
import java.util.HashMap;
|
|
import java.util.Properties;
|
|
import java.util.TreeMap;
|
|
import java.util.zip.GZIPInputStream;
|
|
import java.util.zip.InflaterInputStream;
|
|
|
|
import javax.imageio.ImageIO;
|
|
import javax.xml.parsers.DocumentBuilder;
|
|
import javax.xml.parsers.DocumentBuilderFactory;
|
|
|
|
import org.w3c.dom.Document;
|
|
import org.w3c.dom.NamedNodeMap;
|
|
import org.w3c.dom.Node;
|
|
import org.w3c.dom.NodeList;
|
|
import org.xml.sax.EntityResolver;
|
|
import org.xml.sax.InputSource;
|
|
import org.xml.sax.SAXException;
|
|
|
|
import tiled.core.AnimatedTile;
|
|
import tiled.core.Map;
|
|
import tiled.core.MapLayer;
|
|
import tiled.core.MapObject;
|
|
import tiled.core.ObjectGroup;
|
|
import tiled.core.Tile;
|
|
import tiled.core.TileLayer;
|
|
import tiled.core.TileSet;
|
|
import tiled.util.Base64;
|
|
import tiled.util.BasicTileCutter;
|
|
import tiled.util.ImageHelper;
|
|
|
|
import com.gpl.rpg.atcontentstudio.model.maps.TMXMap;
|
|
|
|
/**
|
|
* The standard map reader for TMX files. Supports reading .tmx, .tmx.gz and *.tsx files.
|
|
*/
|
|
public class TMXMapReader
|
|
{
|
|
private Map map;
|
|
private String xmlPath;
|
|
private String error;
|
|
private final EntityResolver entityResolver = new MapEntityResolver();
|
|
private TreeMap<Integer, TileSet> tilesetPerFirstGid;
|
|
public final TMXMapReaderSettings settings = new TMXMapReaderSettings();
|
|
private final HashMap<String, TileSet> cachedTilesets = new HashMap<String, TileSet>();
|
|
|
|
public static final class TMXMapReaderSettings {
|
|
public boolean reuseCachedTilesets = false;
|
|
}
|
|
|
|
public TMXMapReader() {
|
|
}
|
|
|
|
String getError() {
|
|
return error;
|
|
}
|
|
|
|
private static String makeUrl(String filename) throws MalformedURLException {
|
|
final String url;
|
|
if (filename.indexOf("://") > 0 || filename.startsWith("file:")) {
|
|
url = filename;
|
|
} else {
|
|
url = new File(filename).toURI().toString();
|
|
}
|
|
return url;
|
|
}
|
|
|
|
private static int reflectFindMethodByName(Class c, String methodName) {
|
|
Method[] methods = c.getMethods();
|
|
for (int i = 0; i < methods.length; i++) {
|
|
if (methods[i].getName().equalsIgnoreCase(methodName)) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
private void reflectInvokeMethod(Object invokeVictim, Method method,
|
|
String[] args) throws Exception
|
|
{
|
|
Class[] parameterTypes = method.getParameterTypes();
|
|
Object[] conformingArguments = new Object[parameterTypes.length];
|
|
|
|
if (args.length < parameterTypes.length) {
|
|
throw new Exception("Insufficient arguments were supplied");
|
|
}
|
|
|
|
for (int i = 0; i < parameterTypes.length; i++) {
|
|
if ("int".equalsIgnoreCase(parameterTypes[i].getName())) {
|
|
conformingArguments[i] = new Integer(args[i]);
|
|
} else if ("float".equalsIgnoreCase(parameterTypes[i].getName())) {
|
|
conformingArguments[i] = new Float(args[i]);
|
|
} else if (parameterTypes[i].getName().endsWith("String")) {
|
|
conformingArguments[i] = args[i];
|
|
} else if ("boolean".equalsIgnoreCase(parameterTypes[i].getName())) {
|
|
conformingArguments[i] = Boolean.valueOf(args[i]);
|
|
} else {
|
|
// Unsupported argument type, defaulting to String
|
|
conformingArguments[i] = args[i];
|
|
}
|
|
}
|
|
|
|
method.invoke(invokeVictim,conformingArguments);
|
|
}
|
|
|
|
private void setOrientation(String o) {
|
|
if ("isometric".equalsIgnoreCase(o)) {
|
|
map.setOrientation(Map.ORIENTATION_ISOMETRIC);
|
|
} else if ("orthogonal".equalsIgnoreCase(o)) {
|
|
map.setOrientation(Map.ORIENTATION_ORTHOGONAL);
|
|
} else if ("hexagonal".equalsIgnoreCase(o)) {
|
|
map.setOrientation(Map.ORIENTATION_HEXAGONAL);
|
|
} else if ("shifted".equalsIgnoreCase(o)) {
|
|
map.setOrientation(Map.ORIENTATION_SHIFTED);
|
|
} else {
|
|
// System.out.println("Unknown orientation '" + o + "'");
|
|
}
|
|
}
|
|
|
|
private static String getAttributeValue(Node node, String attribname) {
|
|
final NamedNodeMap attributes = node.getAttributes();
|
|
String value = null;
|
|
if (attributes != null) {
|
|
Node attribute = attributes.getNamedItem(attribname);
|
|
if (attribute != null) {
|
|
value = attribute.getNodeValue();
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
|
|
private static int getAttribute(Node node, String attribname, int def) {
|
|
final String attr = getAttributeValue(node, attribname);
|
|
if (attr != null) {
|
|
return Integer.parseInt(attr);
|
|
} else {
|
|
return def;
|
|
}
|
|
}
|
|
|
|
private Object unmarshalClass(Class reflector, Node node)
|
|
throws InstantiationException, IllegalAccessException,
|
|
InvocationTargetException {
|
|
Constructor cons = null;
|
|
try {
|
|
cons = reflector.getConstructor((Class[]) null);
|
|
} catch (SecurityException e1) {
|
|
e1.printStackTrace();
|
|
} catch (NoSuchMethodException e1) {
|
|
e1.printStackTrace();
|
|
return null;
|
|
}
|
|
Object o = cons.newInstance((Object[]) null);
|
|
Node n;
|
|
|
|
Method[] methods = reflector.getMethods();
|
|
NamedNodeMap nnm = node.getAttributes();
|
|
|
|
if (nnm != null) {
|
|
for (int i = 0; i < nnm.getLength(); i++) {
|
|
n = nnm.item(i);
|
|
|
|
try {
|
|
int j = reflectFindMethodByName(reflector,
|
|
"set" + n.getNodeName());
|
|
if (j >= 0) {
|
|
reflectInvokeMethod(o,methods[j],
|
|
new String [] {n.getNodeValue()});
|
|
} else {
|
|
// System.out.println("Unsupported attribute '" +
|
|
// n.getNodeName() + "' on <" +
|
|
// node.getNodeName() + "> tag");
|
|
}
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
return o;
|
|
}
|
|
|
|
private BufferedImage unmarshalImage(Node t, String baseDir) throws IOException
|
|
{
|
|
BufferedImage img = null;
|
|
|
|
String source = getAttributeValue(t, "source");
|
|
|
|
if (source != null) {
|
|
if (checkRoot(source)) {
|
|
source = makeUrl(source);
|
|
} else {
|
|
source = makeUrl(baseDir + source);
|
|
}
|
|
img = ImageIO.read(new URL(source));
|
|
} else {
|
|
NodeList nl = t.getChildNodes();
|
|
|
|
for (int i = 0; i < nl.getLength(); i++) {
|
|
Node node = nl.item(i);
|
|
if ("data".equals(node.getNodeName())) {
|
|
Node cdata = node.getFirstChild();
|
|
if (cdata != null) {
|
|
String sdata = cdata.getNodeValue();
|
|
char[] charArray = sdata.trim().toCharArray();
|
|
byte[] imageData = Base64.decode(charArray);
|
|
img = ImageHelper.bytesToImage(imageData);
|
|
|
|
// Deriving a scaled instance, even if it has the same
|
|
// size, somehow makes drawing of the tiles a lot
|
|
// faster on various systems (seen on Linux, Windows
|
|
// and MacOS X).
|
|
img = (BufferedImage) img.getScaledInstance(
|
|
img.getWidth(null), img.getHeight(null),
|
|
Image.SCALE_FAST);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return img;
|
|
}
|
|
|
|
private TileSet unmarshalTilesetFile(InputStream in, String filename, TMXMap tmxMap)
|
|
throws Exception
|
|
{
|
|
TileSet set = null;
|
|
Node tsNode;
|
|
|
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
|
try {
|
|
DocumentBuilder builder = factory.newDocumentBuilder();
|
|
//builder.setErrorHandler(new XMLErrorHandler());
|
|
Document tsDoc = builder.parse(in, ".");
|
|
|
|
String xmlPathSave = xmlPath;
|
|
if (filename.indexOf(File.separatorChar) >= 0) {
|
|
xmlPath = filename.substring(0,
|
|
filename.lastIndexOf(File.separatorChar) + 1);
|
|
}
|
|
|
|
NodeList tsNodeList = tsDoc.getElementsByTagName("tileset");
|
|
|
|
// There can be only one tileset in a .tsx file.
|
|
tsNode = tsNodeList.item(0);
|
|
if (tsNode != null) {
|
|
set = unmarshalTileset(tsNode, tmxMap);
|
|
if (set.getSource() != null) {
|
|
// System.out.println("Recursive external tilesets are not supported.");
|
|
}
|
|
set.setSource(filename);
|
|
}
|
|
|
|
xmlPath = xmlPathSave;
|
|
} catch (SAXException e) {
|
|
error = "Failed while loading " + filename + ": " +
|
|
e.getLocalizedMessage();
|
|
}
|
|
|
|
return set;
|
|
}
|
|
|
|
private TileSet unmarshalTileset(Node t, TMXMap tmxMap) throws Exception {
|
|
String source = getAttributeValue(t, "source");
|
|
String basedir = getAttributeValue(t, "basedir");
|
|
int firstGid = getAttribute(t, "firstgid", 1);
|
|
|
|
String tilesetBaseDir = xmlPath;
|
|
|
|
if (basedir != null) {
|
|
tilesetBaseDir = basedir; //makeUrl(basedir);
|
|
}
|
|
|
|
if (source != null) {
|
|
String filename = tilesetBaseDir + source;
|
|
//if (checkRoot(source)) {
|
|
// filename = makeUrl(source);
|
|
//}
|
|
|
|
TileSet ext = null;
|
|
|
|
try {
|
|
InputStream in = new URL(makeUrl(filename)).openStream();
|
|
ext = unmarshalTilesetFile(in, filename, tmxMap);
|
|
setFirstGidForTileset(ext, firstGid);
|
|
} catch (FileNotFoundException fnf) {
|
|
error = "Could not find external tileset file " + filename;
|
|
}
|
|
|
|
if (ext == null) {
|
|
error = "Tileset " + source + " was not loaded correctly!";
|
|
}
|
|
|
|
return ext;
|
|
}
|
|
else {
|
|
final int tileWidth = getAttribute(t, "tilewidth", map != null ? map.getTileWidth() : 0);
|
|
final int tileHeight = getAttribute(t, "tileheight", map != null ? map.getTileHeight() : 0);
|
|
final int tileSpacing = getAttribute(t, "spacing", 0);
|
|
final int tileMargin = getAttribute(t, "margin", 0);
|
|
|
|
final String name = getAttributeValue(t, "name");
|
|
|
|
TileSet set;
|
|
if (settings.reuseCachedTilesets) {
|
|
set = cachedTilesets.get(name);
|
|
if (set != null) {
|
|
setFirstGidForTileset(set, firstGid);
|
|
return set;
|
|
}
|
|
set = new TileSet();
|
|
cachedTilesets.put(name, set);
|
|
} else {
|
|
set = new TileSet();
|
|
}
|
|
|
|
set.setName(name);
|
|
set.setBaseDir(basedir);
|
|
setFirstGidForTileset(set, firstGid);
|
|
|
|
boolean hasTilesetImage = false;
|
|
NodeList children = t.getChildNodes();
|
|
|
|
for (int i = 0; i < children.getLength(); i++) {
|
|
Node child = children.item(i);
|
|
|
|
if (child.getNodeName().equalsIgnoreCase("image")) {
|
|
if (hasTilesetImage) {
|
|
// System.out.println("Ignoring illegal image element after tileset image.");
|
|
continue;
|
|
}
|
|
|
|
String imgSource = getAttributeValue(child, "source");
|
|
String transStr = getAttributeValue(child, "trans");
|
|
|
|
if (imgSource != null) {
|
|
// Not a shared image, but an entire set in one image
|
|
// file. There should be only one image element in this
|
|
// case.
|
|
|
|
if (tmxMap.getProject().getSpritesheet(name) != null) {
|
|
set.loadFromProject(name, tmxMap, tileWidth, tileHeight);
|
|
set.sheetDimensions = new Rectangle();
|
|
set.sheetDimensions.width = getAttribute(child, "width", 0);
|
|
set.sheetDimensions.height = getAttribute(child, "height", 0);
|
|
} else {
|
|
|
|
|
|
hasTilesetImage = true;
|
|
|
|
// FIXME: importTileBitmap does not fully support URLs
|
|
String sourcePath = imgSource;
|
|
if (! new File(imgSource).isAbsolute()) {
|
|
sourcePath = tilesetBaseDir + imgSource;
|
|
}
|
|
|
|
if (transStr != null) {
|
|
if (transStr.startsWith("#"))
|
|
transStr = transStr.substring(1);
|
|
|
|
int colorInt = Integer.parseInt(transStr, 16);
|
|
Color color = new Color(colorInt);
|
|
set.setTransparentColor(color);
|
|
}
|
|
|
|
set.importTileBitmap(sourcePath, new BasicTileCutter(
|
|
tileWidth, tileHeight, tileSpacing, tileMargin));
|
|
}
|
|
}
|
|
}
|
|
else if (child.getNodeName().equalsIgnoreCase("tile")) {
|
|
Tile tile = unmarshalTile(set, child, tilesetBaseDir);
|
|
if (!hasTilesetImage || tile.getId() > set.getMaxTileId()) {
|
|
set.addTile(tile);
|
|
} else {
|
|
Tile myTile = set.getTile(tile.getId());
|
|
myTile.setProperties(tile.getProperties());
|
|
//TODO: there is the possibility here of overlaying images,
|
|
// which some people may want
|
|
}
|
|
}
|
|
}
|
|
|
|
return set;
|
|
}
|
|
}
|
|
|
|
private MapObject readMapObject(Node t) throws Exception {
|
|
final String name = getAttributeValue(t, "name");
|
|
final String type = getAttributeValue(t, "type");
|
|
final int x = getAttribute(t, "x", 0);
|
|
final int y = getAttribute(t, "y", 0);
|
|
final int width = getAttribute(t, "width", 0);
|
|
final int height = getAttribute(t, "height", 0);
|
|
|
|
MapObject obj = new MapObject(x, y, width, height);
|
|
if (name != null)
|
|
obj.setName(name);
|
|
if (type != null)
|
|
obj.setType(type);
|
|
|
|
NodeList children = t.getChildNodes();
|
|
for (int i = 0; i < children.getLength(); i++) {
|
|
Node child = children.item(i);
|
|
if ("image".equalsIgnoreCase(child.getNodeName())) {
|
|
String source = getAttributeValue(child, "source");
|
|
if (source != null) {
|
|
if (! new File(source).isAbsolute()) {
|
|
source = xmlPath + source;
|
|
}
|
|
obj.setImageSource(source);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
Properties props = new Properties();
|
|
readProperties(children, props);
|
|
|
|
obj.setProperties(props);
|
|
return obj;
|
|
}
|
|
|
|
/**
|
|
* Reads properties from amongst the given children. When a "properties"
|
|
* element is encountered, it recursively calls itself with the children
|
|
* of this node. This function ensures backward compatibility with tmx
|
|
* version 0.99a.
|
|
*
|
|
* Support for reading property values stored as character data was added
|
|
* in Tiled 0.7.0 (tmx version 0.99c).
|
|
*
|
|
* @param children the children amongst which to find properties
|
|
* @param props the properties object to set the properties of
|
|
*/
|
|
private static void readProperties(NodeList children, Properties props) {
|
|
for (int i = 0; i < children.getLength(); i++) {
|
|
Node child = children.item(i);
|
|
if ("property".equalsIgnoreCase(child.getNodeName())) {
|
|
final String key = getAttributeValue(child, "name");
|
|
String value = getAttributeValue(child, "value");
|
|
if (value == null) {
|
|
Node grandChild = child.getFirstChild();
|
|
if (grandChild != null) {
|
|
value = grandChild.getNodeValue();
|
|
if (value != null)
|
|
value = value.trim();
|
|
}
|
|
}
|
|
if (value != null)
|
|
props.setProperty(key, value);
|
|
}
|
|
else if ("properties".equals(child.getNodeName())) {
|
|
readProperties(child.getChildNodes(), props);
|
|
}
|
|
}
|
|
}
|
|
|
|
private Tile unmarshalTile(TileSet set, Node t, String baseDir)
|
|
throws Exception
|
|
{
|
|
Tile tile = null;
|
|
NodeList children = t.getChildNodes();
|
|
boolean isAnimated = false;
|
|
|
|
for (int i = 0; i < children.getLength(); i++) {
|
|
Node child = children.item(i);
|
|
if ("animation".equalsIgnoreCase(child.getNodeName())) {
|
|
isAnimated = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
try {
|
|
if (isAnimated) {
|
|
tile = (Tile) unmarshalClass(AnimatedTile.class, t);
|
|
} else {
|
|
tile = (Tile) unmarshalClass(Tile.class, t);
|
|
}
|
|
} catch (Exception e) {
|
|
error = "Failed creating tile: " + e.getLocalizedMessage();
|
|
return tile;
|
|
}
|
|
|
|
tile.setTileSet(set);
|
|
|
|
readProperties(children, tile.getProperties());
|
|
|
|
for (int i = 0; i < children.getLength(); i++) {
|
|
Node child = children.item(i);
|
|
if ("image".equalsIgnoreCase(child.getNodeName())) {
|
|
int id = getAttribute(child, "id", -1);
|
|
BufferedImage img = unmarshalImage(child, baseDir);
|
|
tile.setImage(img);
|
|
} else if ("animation".equalsIgnoreCase(child.getNodeName())) {
|
|
// TODO: fill this in once TMXMapWriter is complete
|
|
}
|
|
}
|
|
|
|
return tile;
|
|
}
|
|
|
|
private MapLayer unmarshalObjectGroup(Node t) throws Exception {
|
|
ObjectGroup og = null;
|
|
try {
|
|
og = (ObjectGroup)unmarshalClass(ObjectGroup.class, t);
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
return og;
|
|
}
|
|
|
|
final int offsetX = getAttribute(t, "x", 0);
|
|
final int offsetY = getAttribute(t, "y", 0);
|
|
og.setOffset(offsetX, offsetY);
|
|
|
|
// Add all objects from the objects group
|
|
NodeList children = t.getChildNodes();
|
|
|
|
for (int i = 0; i < children.getLength(); i++) {
|
|
Node child = children.item(i);
|
|
if ("object".equalsIgnoreCase(child.getNodeName())) {
|
|
og.addObject(readMapObject(child));
|
|
}
|
|
}
|
|
|
|
Properties props = new Properties();
|
|
readProperties(children, props);
|
|
og.setProperties(props);
|
|
|
|
return og;
|
|
}
|
|
|
|
/**
|
|
* Loads a map layer from a layer node.
|
|
* @param t the node representing the "layer" element
|
|
* @return the loaded map layer
|
|
* @throws Exception
|
|
*/
|
|
private MapLayer readLayer(Node t, TMXMap tmxMap) throws Exception {
|
|
final int layerWidth = getAttribute(t, "width", map.getWidth());
|
|
final int layerHeight = getAttribute(t, "height", map.getHeight());
|
|
|
|
TileLayer ml = new TileLayer(layerWidth, layerHeight);
|
|
|
|
final int offsetX = getAttribute(t, "x", 0);
|
|
final int offsetY = getAttribute(t, "y", 0);
|
|
final int visible = getAttribute(t, "visible", 1);
|
|
String opacity = getAttributeValue(t, "opacity");
|
|
|
|
ml.setName(getAttributeValue(t, "name"));
|
|
|
|
if (opacity != null) {
|
|
ml.setOpacity(Float.parseFloat(opacity));
|
|
}
|
|
|
|
readProperties(t.getChildNodes(), ml.getProperties());
|
|
|
|
for (Node child = t.getFirstChild(); child != null;
|
|
child = child.getNextSibling())
|
|
{
|
|
String nodeName = child.getNodeName();
|
|
if ("data".equalsIgnoreCase(nodeName)) {
|
|
String encoding = getAttributeValue(child, "encoding");
|
|
|
|
if (encoding != null && "base64".equalsIgnoreCase(encoding)) {
|
|
Node cdata = child.getFirstChild();
|
|
if (cdata != null) {
|
|
char[] enc = cdata.getNodeValue().trim().toCharArray();
|
|
byte[] dec = Base64.decode(enc);
|
|
ByteArrayInputStream bais = new ByteArrayInputStream(dec);
|
|
InputStream is;
|
|
|
|
String comp = getAttributeValue(child, "compression");
|
|
|
|
if ("gzip".equalsIgnoreCase(comp)) {
|
|
final int len = layerWidth * layerHeight * 4;
|
|
is = new GZIPInputStream(bais, len);
|
|
} else if ("zlib".equalsIgnoreCase(comp)) {
|
|
is = new InflaterInputStream(bais);
|
|
} else if (comp != null && !comp.isEmpty()) {
|
|
throw new IOException("Unrecognized compression method \"" + comp + "\" for map layer " + ml.getName());
|
|
} else {
|
|
is = bais;
|
|
}
|
|
|
|
for (int y = 0; y < ml.getHeight(); y++) {
|
|
for (int x = 0; x < ml.getWidth(); x++) {
|
|
int tileId = 0;
|
|
tileId |= is.read();
|
|
tileId |= is.read() << 8;
|
|
tileId |= is.read() << 16;
|
|
tileId |= is.read() << 24;
|
|
|
|
java.util.Map.Entry<Integer, TileSet> ts = findTileSetForTileGID(tileId);
|
|
if (ts != null) {
|
|
ml.setTileAt(x, y,
|
|
ts.getValue().getTile(tileId - ts.getKey()));
|
|
if (ts.getValue().getSpritesheet() != null) {
|
|
tmxMap.usedSpritesheets.add(ts.getValue().getSpritesheet());
|
|
ts.getValue().getSpritesheet().addBacklink(tmxMap);
|
|
}
|
|
} else {
|
|
ml.setTileAt(x, y, null);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
int x = 0, y = 0;
|
|
for (Node dataChild = child.getFirstChild();
|
|
dataChild != null;
|
|
dataChild = dataChild.getNextSibling())
|
|
{
|
|
if ("tile".equalsIgnoreCase(dataChild.getNodeName())) {
|
|
int tileId = getAttribute(dataChild, "gid", -1);
|
|
java.util.Map.Entry<Integer, TileSet> ts = findTileSetForTileGID(tileId);
|
|
if (ts != null) {
|
|
ml.setTileAt(x, y,
|
|
ts.getValue().getTile(tileId - ts.getKey()));
|
|
if (ts.getValue().getSpritesheet() != null) {
|
|
tmxMap.usedSpritesheets.add(ts.getValue().getSpritesheet());
|
|
ts.getValue().getSpritesheet().addBacklink(tmxMap);
|
|
}
|
|
} else {
|
|
ml.setTileAt(x, y, null);
|
|
}
|
|
|
|
x++;
|
|
if (x == ml.getWidth()) {
|
|
x = 0; y++;
|
|
}
|
|
if (y == ml.getHeight()) { break; }
|
|
}
|
|
}
|
|
}
|
|
} else if ("tileproperties".equalsIgnoreCase(nodeName)) {
|
|
for (Node tpn = child.getFirstChild();
|
|
tpn != null;
|
|
tpn = tpn.getNextSibling())
|
|
{
|
|
if ("tile".equalsIgnoreCase(tpn.getNodeName())) {
|
|
int x = getAttribute(tpn, "x", -1);
|
|
int y = getAttribute(tpn, "y", -1);
|
|
|
|
Properties tip = new Properties();
|
|
|
|
readProperties(tpn.getChildNodes(), tip);
|
|
ml.setTileInstancePropertiesAt(x, y, tip);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// This is done at the end, otherwise the offset is applied during
|
|
// the loading of the tiles.
|
|
ml.setOffset(offsetX, offsetY);
|
|
|
|
// Invisible layers are automatically locked, so it is important to
|
|
// set the layer to potentially invisible _after_ the layer data is
|
|
// loaded.
|
|
// todo: Shouldn't this be just a user interface feature, rather than
|
|
// todo: something to keep in mind at this level?
|
|
ml.setVisible(visible == 1);
|
|
|
|
return ml;
|
|
}
|
|
|
|
private void buildMap(Document doc, TMXMap tmxMap) throws Exception {
|
|
Node item, mapNode;
|
|
|
|
mapNode = doc.getDocumentElement();
|
|
|
|
if (!"map".equals(mapNode.getNodeName())) {
|
|
throw new Exception("Not a valid tmx map file.");
|
|
}
|
|
|
|
// Get the map dimensions and create the map
|
|
int mapWidth = getAttribute(mapNode, "width", 0);
|
|
int mapHeight = getAttribute(mapNode, "height", 0);
|
|
|
|
if (mapWidth > 0 && mapHeight > 0) {
|
|
map = new Map(mapWidth, mapHeight);
|
|
} else {
|
|
// Maybe this map is still using the dimensions element
|
|
NodeList l = doc.getElementsByTagName("dimensions");
|
|
for (int i = 0; (item = l.item(i)) != null; i++) {
|
|
if (item.getParentNode() == mapNode) {
|
|
mapWidth = getAttribute(item, "width", 0);
|
|
mapHeight = getAttribute(item, "height", 0);
|
|
|
|
if (mapWidth > 0 && mapHeight > 0) {
|
|
map = new Map(mapWidth, mapHeight);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (map == null) {
|
|
throw new Exception("Couldn't locate map dimensions.");
|
|
}
|
|
|
|
// Load other map attributes
|
|
String orientation = getAttributeValue(mapNode, "orientation");
|
|
int tileWidth = getAttribute(mapNode, "tilewidth", 0);
|
|
int tileHeight = getAttribute(mapNode, "tileheight", 0);
|
|
|
|
if (tileWidth > 0) {
|
|
map.setTileWidth(tileWidth);
|
|
}
|
|
if (tileHeight > 0) {
|
|
map.setTileHeight(tileHeight);
|
|
}
|
|
|
|
if (orientation != null) {
|
|
setOrientation(orientation);
|
|
} else {
|
|
setOrientation("orthogonal");
|
|
}
|
|
|
|
// Load properties
|
|
readProperties(mapNode.getChildNodes(), map.getProperties());
|
|
|
|
// Load tilesets first, in case order is munged
|
|
tilesetPerFirstGid = new TreeMap<Integer, TileSet>();
|
|
NodeList l = doc.getElementsByTagName("tileset");
|
|
for (int i = 0; (item = l.item(i)) != null; i++) {
|
|
map.addTileset(unmarshalTileset(item, tmxMap));
|
|
}
|
|
|
|
// Load the layers and objectgroups
|
|
for (Node sibs = mapNode.getFirstChild(); sibs != null;
|
|
sibs = sibs.getNextSibling())
|
|
{
|
|
if ("layer".equals(sibs.getNodeName())) {
|
|
MapLayer layer = readLayer(sibs, tmxMap);
|
|
if (layer != null) {
|
|
map.addLayer(layer);
|
|
}
|
|
}
|
|
else if ("objectgroup".equals(sibs.getNodeName())) {
|
|
MapLayer layer = unmarshalObjectGroup(sibs);
|
|
if (layer != null) {
|
|
map.addLayer(layer);
|
|
}
|
|
}
|
|
}
|
|
tilesetPerFirstGid = null;
|
|
}
|
|
|
|
private Map unmarshal(Reader reader, TMXMap tmxMap) throws Exception {
|
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
|
Document doc;
|
|
try {
|
|
factory.setIgnoringComments(true);
|
|
factory.setIgnoringElementContentWhitespace(true);
|
|
factory.setExpandEntityReferences(false);
|
|
DocumentBuilder builder = factory.newDocumentBuilder();
|
|
builder.setEntityResolver(entityResolver);
|
|
InputSource insrc = new InputSource(reader);
|
|
insrc.setSystemId(xmlPath);
|
|
insrc.setEncoding("UTF-8");
|
|
doc = builder.parse(insrc);
|
|
} catch (SAXException e) {
|
|
e.printStackTrace();
|
|
throw new Exception("Error while parsing map file: " +
|
|
e.toString());
|
|
}
|
|
|
|
buildMap(doc, tmxMap);
|
|
|
|
return map;
|
|
}
|
|
private Map unmarshal(InputStream in, TMXMap tmxMap) throws Exception {
|
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
|
Document doc;
|
|
try {
|
|
factory.setIgnoringComments(true);
|
|
factory.setIgnoringElementContentWhitespace(true);
|
|
factory.setExpandEntityReferences(false);
|
|
DocumentBuilder builder = factory.newDocumentBuilder();
|
|
builder.setEntityResolver(entityResolver);
|
|
InputSource insrc = new InputSource(in);
|
|
insrc.setSystemId(xmlPath);
|
|
insrc.setEncoding("UTF-8");
|
|
doc = builder.parse(insrc);
|
|
} catch (SAXException e) {
|
|
e.printStackTrace();
|
|
throw new Exception("Error while parsing map file: " +
|
|
e.toString());
|
|
}
|
|
|
|
buildMap(doc, tmxMap);
|
|
|
|
return map;
|
|
}
|
|
|
|
|
|
public Map readMap(String filename, TMXMap tmxMap) throws Exception {
|
|
xmlPath = filename.substring(0,
|
|
filename.lastIndexOf(File.separatorChar) + 1);
|
|
|
|
String xmlFile = makeUrl(filename);
|
|
//xmlPath = makeUrl(xmlPath);
|
|
|
|
URL url = new URL(xmlFile);
|
|
InputStream is = url.openStream();
|
|
|
|
// Wrap with GZIP decoder for .tmx.gz files
|
|
if (filename.endsWith(".gz")) {
|
|
is = new GZIPInputStream(is);
|
|
}
|
|
|
|
Map unmarshalledMap = unmarshal(is, tmxMap);
|
|
unmarshalledMap.setFilename(filename);
|
|
|
|
map = null;
|
|
|
|
return unmarshalledMap;
|
|
}
|
|
|
|
public Map readMap(InputStream in, TMXMap tmxMap) throws Exception {
|
|
xmlPath = makeUrl(".");
|
|
|
|
Map unmarshalledMap = unmarshal(in, tmxMap);
|
|
|
|
//unmarshalledMap.setFilename(xmlFile)
|
|
//
|
|
return unmarshalledMap;
|
|
}
|
|
|
|
public Map readMap(Reader reader, TMXMap tmxMap) throws Exception {
|
|
xmlPath = makeUrl(".");
|
|
|
|
Map unmarshalledMap = unmarshal(reader, tmxMap);
|
|
|
|
//unmarshalledMap.setFilename(xmlFile)
|
|
//
|
|
return unmarshalledMap;
|
|
}
|
|
|
|
public TileSet readTileset(String filename, TMXMap tmxMap) throws Exception {
|
|
String xmlFile = filename;
|
|
|
|
xmlPath = filename.substring(0,
|
|
filename.lastIndexOf(File.separatorChar) + 1);
|
|
|
|
xmlFile = makeUrl(xmlFile);
|
|
xmlPath = makeUrl(xmlPath);
|
|
|
|
URL url = new URL(xmlFile);
|
|
return unmarshalTilesetFile(url.openStream(), filename, tmxMap);
|
|
}
|
|
|
|
public TileSet readTileset(InputStream in, TMXMap tmxMap) throws Exception {
|
|
return unmarshalTilesetFile(in, ".", tmxMap);
|
|
}
|
|
|
|
public boolean accept(File pathName) {
|
|
try {
|
|
String path = pathName.getCanonicalPath();
|
|
if (path.endsWith(".tmx") || path.endsWith(".tsx") ||
|
|
path.endsWith(".tmx.gz")) {
|
|
return true;
|
|
}
|
|
} catch (IOException e) {}
|
|
return false;
|
|
}
|
|
|
|
private class MapEntityResolver implements EntityResolver
|
|
{
|
|
public InputSource resolveEntity(String publicId, String systemId) {
|
|
if (systemId.equals("http://mapeditor.org/dtd/1.0/map.dtd")) {
|
|
return new InputSource(TMXMapReader.class.getResourceAsStream(
|
|
"resources/map.dtd"));
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This utility function will check the specified string to see if it
|
|
* starts with one of the OS root designations. (Ex.: '/' on Unix, 'C:' on
|
|
* Windows)
|
|
*
|
|
* @param filename a filename to check for absolute or relative path
|
|
* @return <code>true</code> if the specified filename starts with a
|
|
* filesystem root, <code>false</code> otherwise.
|
|
*/
|
|
public static boolean checkRoot(String filename) {
|
|
File[] roots = File.listRoots();
|
|
|
|
for (File root : roots) {
|
|
try {
|
|
String canonicalRoot = root.getCanonicalPath().toLowerCase();
|
|
if (filename.toLowerCase().startsWith(canonicalRoot)) {
|
|
return true;
|
|
}
|
|
} catch (IOException e) {
|
|
// Do we care?
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get the tile set and its corresponding firstgid that matches the given
|
|
* global tile id.
|
|
*
|
|
*
|
|
* @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
|
|
*/
|
|
private java.util.Map.Entry<Integer, TileSet> findTileSetForTileGID(int gid) {
|
|
return tilesetPerFirstGid.floorEntry(gid);
|
|
}
|
|
|
|
private void setFirstGidForTileset(TileSet tileset, int firstGid) {
|
|
tilesetPerFirstGid.put(firstGid, tileset);
|
|
}
|
|
|
|
}
|