Skip to content

Commit ddb0901

Browse files
committed
feat: support modern Tiled TMX format features in libtiled-java
1 parent 193b61c commit ddb0901

File tree

7 files changed

+535
-28
lines changed

7 files changed

+535
-28
lines changed

util/java/libtiled-java/src/main/java/org/mapeditor/core/Properties.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,21 @@ public void setProperty(String name, String value) {
6464
properties.add(property);
6565
}
6666

67+
/**
68+
* setProperty with type.
69+
*
70+
* @param name a {@link java.lang.String} object.
71+
* @param value a {@link java.lang.String} object.
72+
* @param type a {@link org.mapeditor.core.PropertyType} object.
73+
*/
74+
public void setProperty(String name, String value, PropertyType type) {
75+
Property property = new Property();
76+
property.setName(name);
77+
property.setValue(value);
78+
property.setType(type);
79+
properties.add(property);
80+
}
81+
6782
/**
6883
* getProperty.
6984
*

util/java/libtiled-java/src/main/java/org/mapeditor/io/TMXMapReader.java

Lines changed: 155 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,18 @@ private static void readProperties(NodeList children, Properties props) {
482482
}
483483
}
484484
if (value != null) {
485-
props.setProperty(key, value);
485+
final String typeStr = getAttributeValue(child, "type");
486+
if (typeStr != null && !typeStr.isEmpty()) {
487+
try {
488+
org.mapeditor.core.PropertyType type =
489+
org.mapeditor.core.PropertyType.fromValue(typeStr);
490+
props.setProperty(key, value, type);
491+
} catch (IllegalArgumentException e) {
492+
props.setProperty(key, value);
493+
}
494+
} else {
495+
props.setProperty(key, value);
496+
}
486497
}
487498
} else if ("properties".equals(child.getNodeName())) {
488499
readProperties(child.getChildNodes(), props);
@@ -656,6 +667,21 @@ private TileLayer readLayer(Node t) throws Exception {
656667
ml.setOpacity(Float.parseFloat(opacity));
657668
}
658669

670+
final String tintColor = getAttributeValue(t, "tintcolor");
671+
if (tintColor != null) {
672+
ml.setTintcolor(tintColor);
673+
}
674+
675+
final double parallaxx = getDoubleAttribute(t, "parallaxx", 1.0);
676+
if (parallaxx != 1.0) {
677+
ml.setParallaxx(parallaxx);
678+
}
679+
680+
final double parallaxy = getDoubleAttribute(t, "parallaxy", 1.0);
681+
if (parallaxy != 1.0) {
682+
ml.setParallaxy(parallaxy);
683+
}
684+
659685
readProperties(t.getChildNodes(), ml.getProperties());
660686

661687
for (Node child = t.getFirstChild(); child != null;
@@ -665,7 +691,66 @@ private TileLayer readLayer(Node t) throws Exception {
665691
String encoding = getAttributeValue(child, "encoding");
666692
String comp = getAttributeValue(child, "compression");
667693

668-
if ("base64".equalsIgnoreCase(encoding)) {
694+
// Check for chunk children (infinite maps)
695+
boolean hasChunks = false;
696+
for (Node chunkCheck = child.getFirstChild(); chunkCheck != null;
697+
chunkCheck = chunkCheck.getNextSibling()) {
698+
if ("chunk".equalsIgnoreCase(chunkCheck.getNodeName())) {
699+
hasChunks = true;
700+
break;
701+
}
702+
}
703+
704+
if (hasChunks) {
705+
// Infinite map: compute bounding box from all chunks
706+
int minX = Integer.MAX_VALUE, minY = Integer.MAX_VALUE;
707+
int maxX = Integer.MIN_VALUE, maxY = Integer.MIN_VALUE;
708+
for (Node chunkNode = child.getFirstChild(); chunkNode != null;
709+
chunkNode = chunkNode.getNextSibling()) {
710+
if ("chunk".equalsIgnoreCase(chunkNode.getNodeName())) {
711+
int cx = getAttribute(chunkNode, "x", 0);
712+
int cy = getAttribute(chunkNode, "y", 0);
713+
int cw = getAttribute(chunkNode, "width", 0);
714+
int ch = getAttribute(chunkNode, "height", 0);
715+
minX = Math.min(minX, cx);
716+
minY = Math.min(minY, cy);
717+
maxX = Math.max(maxX, cx + cw);
718+
maxY = Math.max(maxY, cy + ch);
719+
}
720+
}
721+
int totalWidth = maxX - minX;
722+
int totalHeight = maxY - minY;
723+
ml = new TileLayer(new java.awt.Rectangle(minX, minY, totalWidth, totalHeight));
724+
ml.setId(layerId);
725+
ml.setName(getAttributeValue(t, "name"));
726+
if (opacity != null) {
727+
ml.setOpacity(Float.parseFloat(opacity));
728+
}
729+
if (tintColor != null) {
730+
ml.setTintcolor(tintColor);
731+
}
732+
if (parallaxx != 1.0) {
733+
ml.setParallaxx(parallaxx);
734+
}
735+
if (parallaxy != 1.0) {
736+
ml.setParallaxy(parallaxy);
737+
}
738+
readProperties(t.getChildNodes(), ml.getProperties());
739+
740+
// Read each chunk
741+
for (Node chunkNode = child.getFirstChild(); chunkNode != null;
742+
chunkNode = chunkNode.getNextSibling()) {
743+
if (!"chunk".equalsIgnoreCase(chunkNode.getNodeName())) {
744+
continue;
745+
}
746+
int cx = getAttribute(chunkNode, "x", 0);
747+
int cy = getAttribute(chunkNode, "y", 0);
748+
int cw = getAttribute(chunkNode, "width", 0);
749+
int ch = getAttribute(chunkNode, "height", 0);
750+
751+
readChunkData(ml, chunkNode, encoding, comp, cx, cy, cw, ch);
752+
}
753+
} else if ("base64".equalsIgnoreCase(encoding)) {
669754
Node cdata = child.getFirstChild();
670755
if (cdata != null) {
671756
String enc = cdata.getNodeValue().trim();
@@ -777,6 +862,74 @@ private TileLayer readLayer(Node t) throws Exception {
777862

778863

779864

865+
/**
866+
* Reads tile data from a chunk node and places tiles in the layer at the
867+
* correct position.
868+
*/
869+
private void readChunkData(TileLayer ml, Node chunkNode, String encoding,
870+
String comp, int cx, int cy, int cw, int ch) throws IOException {
871+
if ("base64".equalsIgnoreCase(encoding)) {
872+
Node cdata = chunkNode.getFirstChild();
873+
if (cdata != null) {
874+
String enc = cdata.getNodeValue().trim();
875+
byte[] dec = DatatypeConverter.parseBase64Binary(enc);
876+
ByteArrayInputStream bais = new ByteArrayInputStream(dec);
877+
InputStream is;
878+
879+
if ("gzip".equalsIgnoreCase(comp)) {
880+
is = new GZIPInputStream(bais, cw * ch * 4);
881+
} else if ("zlib".equalsIgnoreCase(comp)) {
882+
is = new InflaterInputStream(bais);
883+
} else if (comp != null && !comp.isEmpty()) {
884+
throw new IOException("Unrecognized compression method \"" + comp + "\" for chunk");
885+
} else {
886+
is = bais;
887+
}
888+
889+
for (int y = 0; y < ch; y++) {
890+
for (int x = 0; x < cw; x++) {
891+
int tileId = 0;
892+
tileId |= is.read();
893+
tileId |= is.read() << Byte.SIZE;
894+
tileId |= is.read() << Byte.SIZE * 2;
895+
tileId |= is.read() << Byte.SIZE * 3;
896+
897+
setTileAtFromTileId(ml, cy + y, cx + x, tileId);
898+
}
899+
}
900+
}
901+
} else if ("csv".equalsIgnoreCase(encoding)) {
902+
String csvText = chunkNode.getTextContent();
903+
String[] csvTileIds = csvText.trim().split("[\\s]*,[\\s]*");
904+
905+
for (int y = 0; y < ch; y++) {
906+
for (int x = 0; x < cw; x++) {
907+
String gid = csvTileIds[x + y * cw];
908+
long tileId = Long.parseLong(gid);
909+
setTileAtFromTileId(ml, cy + y, cx + x, (int) tileId);
910+
}
911+
}
912+
} else {
913+
int x = 0, y = 0;
914+
for (Node dataChild = chunkNode.getFirstChild(); dataChild != null;
915+
dataChild = dataChild.getNextSibling()) {
916+
if ("tile".equalsIgnoreCase(dataChild.getNodeName())) {
917+
int tileId = getAttribute(dataChild, "gid", -1);
918+
setTileAtFromTileId(ml, cy + y, cx + x, tileId);
919+
920+
x++;
921+
if (x == cw) {
922+
x = 0;
923+
y++;
924+
}
925+
if (y == ch) {
926+
break;
927+
}
928+
}
929+
}
930+
}
931+
}
932+
780933
/**
781934
* Helper method to set the tile based on its global id.
782935
*

0 commit comments

Comments
 (0)