1 /*
2  * Lua Tiled Plugin
3  * Copyright 2011, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
4  *
5  * This file is part of Tiled.
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the Free
9  * Software Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15  * more details.
16  *
17  * You should have received a copy of the GNU General Public License along with
18  * this program. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "luaplugin.h"
22 
23 #include "luatablewriter.h"
24 
25 #include "gidmapper.h"
26 #include "grouplayer.h"
27 #include "imagelayer.h"
28 #include "map.h"
29 #include "mapobject.h"
30 #include "objectgroup.h"
31 #include "objecttemplate.h"
32 #include "properties.h"
33 #include "savefile.h"
34 #include "tile.h"
35 #include "tilelayer.h"
36 #include "tileset.h"
37 #include "wangset.h"
38 
39 #include <QCoreApplication>
40 #include <QDir>
41 #include <QFile>
42 
43 /**
44  * See below for an explanation of the different formats. One of these needs
45  * to be defined.
46  */
47 #define POLYGON_FORMAT_FULL
48 //#define POLYGON_FORMAT_PAIRS
49 //#define POLYGON_FORMAT_OPTIMAL
50 
51 using namespace Tiled;
52 
53 namespace Lua {
54 
55 class LuaWriter
56 {
57 public:
LuaWriter(LuaTableWriter & writer,const QDir & dir)58     explicit LuaWriter(LuaTableWriter &writer, const QDir &dir)
59         : mWriter(writer)
60         , mDir(dir)
61     {}
62 
63     void setMinimize(bool minimize);
64 
65     void writeMap(const Tiled::Map *);
66     void writeProperties(const Tiled::Properties &);
67     void writeTileset(const Tiled::Tileset &,
68                       unsigned firstGid,
69                       bool embedded = true);
70     void writeWangSet(const Tiled::WangSet &);
71     void writeLayers(const QList<Tiled::Layer*> &layers,
72                      Tiled::Map::LayerDataFormat format,
73                      int compressionLevel,
74                      QSize chunkSize);
75     void writeTileLayer(const Tiled::TileLayer *,
76                         Tiled::Map::LayerDataFormat, int compressionLevel,
77                         QSize chunkSize);
78     void writeTileLayerData(const Tiled::TileLayer *,
79                             Tiled::Map::LayerDataFormat format,
80                             QRect bounds,
81                             int compressionLevel);
82     void writeObjectGroup(const Tiled::ObjectGroup *,
83                           const char *key = nullptr);
84     void writeImageLayer(const Tiled::ImageLayer *);
85     void writeGroupLayer(const Tiled::GroupLayer *,
86                          Tiled::Map::LayerDataFormat,
87                          int compressionLevel,
88                          QSize chunkSize);
89     void writeMapObject(const Tiled::MapObject *);
90 
91     void writeLayerProperties(const Tiled::Layer *);
92     void writePolygon(const Tiled::MapObject *);
93     void writeTextProperties(const Tiled::MapObject *);
94     void writeColor(const char *name, const QColor &color);
95 
96 private:
97     LuaTableWriter &mWriter;
98     const QDir mDir;
99     Tiled::GidMapper mGidMapper;
100 };
101 
102 
initialize()103 void LuaPlugin::initialize()
104 {
105     addObject(new LuaMapFormat(this));
106     addObject(new LuaTilesetFormat(this));
107 }
108 
write(const Map * map,const QString & fileName,Options options)109 bool LuaMapFormat::write(const Map *map,
110                          const QString &fileName,
111                          Options options)
112 {
113     SaveFile file(fileName);
114 
115     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
116         mError = QCoreApplication::translate("File Errors", "Could not open file for writing.");
117         return false;
118     }
119 
120     LuaTableWriter writer(file.device());
121 
122     LuaWriter luaWriter(writer, QFileInfo(fileName).path());
123     luaWriter.setMinimize(options.testFlag(WriteMinimized));
124     luaWriter.writeMap(map);
125 
126     if (file.error() != QFileDevice::NoError) {
127         mError = file.errorString();
128         return false;
129     }
130 
131     if (!file.commit()) {
132         mError = file.errorString();
133         return false;
134     }
135 
136     return true;
137 }
138 
nameFilter() const139 QString LuaMapFormat::nameFilter() const
140 {
141     return tr("Lua files (*.lua)");
142 }
143 
shortName() const144 QString LuaMapFormat::shortName() const
145 {
146     return QStringLiteral("lua");
147 }
148 
errorString() const149 QString LuaMapFormat::errorString() const
150 {
151     return mError;
152 }
153 
write(const Tileset & tileset,const QString & fileName,Options options)154 bool LuaTilesetFormat::write(const Tileset &tileset,
155                              const QString &fileName,
156                              Options options)
157 {
158     SaveFile file(fileName);
159 
160     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
161         mError = QCoreApplication::translate("File Errors", "Could not open file for writing.");
162         return false;
163     }
164 
165     LuaTableWriter writer(file.device());
166 
167     LuaWriter luaWriter(writer, QFileInfo(fileName).path());
168     luaWriter.setMinimize(options.testFlag(WriteMinimized));
169     luaWriter.writeTileset(tileset, 0, false);
170 
171     if (file.error() != QFileDevice::NoError) {
172         mError = file.errorString();
173         return false;
174     }
175 
176     if (!file.commit()) {
177         mError = file.errorString();
178         return false;
179     }
180 
181     return true;
182 }
183 
nameFilter() const184 QString LuaTilesetFormat::nameFilter() const
185 {
186     return tr("Lua files (*.lua)");
187 }
188 
shortName() const189 QString LuaTilesetFormat::shortName() const
190 {
191     return QStringLiteral("lua");
192 }
193 
errorString() const194 QString LuaTilesetFormat::errorString() const
195 {
196     return mError;
197 }
198 
199 
setMinimize(bool minimize)200 void LuaWriter::setMinimize(bool minimize)
201 {
202     mWriter.setMinimize(minimize);
203 }
204 
writeMap(const Map * map)205 void LuaWriter::writeMap(const Map *map)
206 {
207     mWriter.writeStartDocument();
208     mWriter.writeStartReturnTable();
209 
210     mWriter.writeKeyAndValue("version", "1.5");
211     mWriter.writeKeyAndValue("luaversion", "5.1");
212     mWriter.writeKeyAndValue("tiledversion", QCoreApplication::applicationVersion());
213 
214     const QString orientation = orientationToString(map->orientation());
215     const QString renderOrder = renderOrderToString(map->renderOrder());
216 
217     mWriter.writeKeyAndValue("orientation", orientation);
218     mWriter.writeKeyAndValue("renderorder", renderOrder);
219     mWriter.writeKeyAndValue("width", map->width());
220     mWriter.writeKeyAndValue("height", map->height());
221     mWriter.writeKeyAndValue("tilewidth", map->tileWidth());
222     mWriter.writeKeyAndValue("tileheight", map->tileHeight());
223     mWriter.writeKeyAndValue("nextlayerid", map->nextLayerId());
224     mWriter.writeKeyAndValue("nextobjectid", map->nextObjectId());
225 
226     if (map->orientation() == Map::Hexagonal)
227         mWriter.writeKeyAndValue("hexsidelength", map->hexSideLength());
228 
229     if (map->orientation() == Map::Staggered || map->orientation() == Map::Hexagonal) {
230         mWriter.writeKeyAndValue("staggeraxis",
231                                  staggerAxisToString(map->staggerAxis()));
232         mWriter.writeKeyAndValue("staggerindex",
233                                  staggerIndexToString(map->staggerIndex()));
234     }
235 
236     const QColor &backgroundColor = map->backgroundColor();
237     if (backgroundColor.isValid())
238         writeColor("backgroundcolor", backgroundColor);
239 
240     writeProperties(map->properties());
241 
242     mWriter.writeStartTable("tilesets");
243 
244     mGidMapper.clear();
245     unsigned firstGid = 1;
246     for (const SharedTileset &tileset : map->tilesets()) {
247         writeTileset(*tileset, firstGid);
248         mGidMapper.insert(firstGid, tileset);
249         firstGid += tileset->nextTileId();
250     }
251     mWriter.writeEndTable();
252 
253     writeLayers(map->layers(), map->layerDataFormat(), map->compressionLevel(), map->chunkSize());
254 
255     mWriter.writeEndTable();
256     mWriter.writeEndDocument();
257 }
258 
writeProperties(const Properties & properties)259 void LuaWriter::writeProperties(const Properties &properties)
260 {
261     mWriter.writeStartTable("properties");
262 
263     Properties::const_iterator it = properties.constBegin();
264     Properties::const_iterator it_end = properties.constEnd();
265     for (; it != it_end; ++it) {
266         if (it.value().userType() == objectRefTypeId()) {
267             mWriter.writeStartTable(it.key());
268             mWriter.setSuppressNewlines(true);
269             mWriter.writeKeyAndValue("id", it.value().value<ObjectRef>().id);
270             mWriter.writeEndTable();
271             mWriter.setSuppressNewlines(false);
272         } else {
273             const QVariant value = toExportValue(it.value(), mDir);
274             mWriter.writeQuotedKeyAndValue(it.key(), value);
275         }
276     }
277 
278     mWriter.writeEndTable();
279 }
280 
includeTile(const Tile * tile)281 static bool includeTile(const Tile *tile)
282 {
283     if (!tile->type().isEmpty())
284         return true;
285     if (!tile->properties().isEmpty())
286         return true;
287     if (!tile->imageSource().isEmpty())
288         return true;
289     if (tile->objectGroup())
290         return true;
291     if (tile->isAnimated())
292         return true;
293     if (tile->probability() != 1.0)
294         return true;
295 
296     return false;
297 }
298 
writeTileset(const Tileset & tileset,unsigned firstGid,bool embedded)299 void LuaWriter::writeTileset(const Tileset &tileset,
300                              unsigned firstGid, bool embedded)
301 {
302     if (embedded) {
303         mWriter.writeStartTable();
304     } else {
305         mWriter.writeStartDocument();
306         mWriter.writeStartReturnTable();
307 
308         // Include version in external tilesets
309         mWriter.writeKeyAndValue("version", "1.5");
310         mWriter.writeKeyAndValue("luaversion", "5.1");
311         mWriter.writeKeyAndValue("tiledversion", QCoreApplication::applicationVersion());
312     }
313 
314     mWriter.writeKeyAndValue("name", tileset.name());
315     if (embedded) {
316         mWriter.writeKeyAndValue("firstgid", firstGid);
317 
318         if (tileset.isExternal()) {
319             const QString rel = mDir.relativeFilePath(tileset.fileName());
320             mWriter.writeKeyAndValue("filename", rel);
321 
322             // For those exporting their tilesets separately, it could be
323             // helpful to include a relative reference to the exported file as
324             // well. For consistency I decided to include this separately from
325             // the "filename".
326             if (!tileset.exportFileName.isEmpty()) {
327                 const QString rel = mDir.relativeFilePath(tileset.exportFileName);
328                 mWriter.writeKeyAndValue("exportfilename", rel);
329             }
330 
331             mWriter.writeEndTable(); // tileset
332             return;
333         }
334     }
335 
336     /* Include all tileset information even for external tilesets, since the
337      * external reference is generally a .tsx file (in XML format).
338      */
339     mWriter.writeKeyAndValue("tilewidth", tileset.tileWidth());
340     mWriter.writeKeyAndValue("tileheight", tileset.tileHeight());
341     mWriter.writeKeyAndValue("spacing", tileset.tileSpacing());
342     mWriter.writeKeyAndValue("margin", tileset.margin());
343     mWriter.writeKeyAndValue("columns", tileset.columnCount());
344 
345     if (!tileset.imageSource().isEmpty()) {
346         const QString rel = toFileReference(tileset.imageSource(), mDir);
347         mWriter.writeKeyAndValue("image", rel);
348         mWriter.writeKeyAndValue("imagewidth", tileset.imageWidth());
349         mWriter.writeKeyAndValue("imageheight", tileset.imageHeight());
350     }
351 
352     if (tileset.transparentColor().isValid()) {
353         mWriter.writeKeyAndValue("transparentcolor",
354                                  tileset.transparentColor().name());
355     }
356 
357     const QColor &backgroundColor = tileset.backgroundColor();
358     if (backgroundColor.isValid())
359         writeColor("backgroundcolor", backgroundColor);
360 
361     mWriter.writeKeyAndValue("objectalignment", alignmentToString(tileset.objectAlignment()));
362 
363     const QPoint offset = tileset.tileOffset();
364     mWriter.writeStartTable("tileoffset");
365     mWriter.writeKeyAndValue("x", offset.x());
366     mWriter.writeKeyAndValue("y", offset.y());
367     mWriter.writeEndTable();
368 
369     const QSize gridSize = tileset.gridSize();
370     mWriter.writeStartTable("grid");
371     mWriter.writeKeyAndValue("orientation", Tileset::orientationToString(tileset.orientation()));
372     mWriter.writeKeyAndValue("width", gridSize.width());
373     mWriter.writeKeyAndValue("height", gridSize.height());
374     mWriter.writeEndTable();
375 
376     writeProperties(tileset.properties());
377 
378     mWriter.writeStartTable("wangsets");
379     for (int i = 0; i < tileset.wangSetCount(); ++i)
380         writeWangSet(*tileset.wangSet(i));
381     mWriter.writeEndTable();
382 
383     mWriter.writeKeyAndValue("tilecount", tileset.tileCount());
384     mWriter.writeStartTable("tiles");
385 
386     const bool includeAllTiles = tileset.anyTileOutOfOrder();
387 
388     for (const Tile *tile : tileset.tiles()) {
389         // For brevity only write tiles with interesting properties
390         if (!includeAllTiles && !includeTile(tile))
391             continue;
392 
393         mWriter.writeStartTable();
394         mWriter.writeKeyAndValue("id", tile->id());
395 
396         if (!tile->type().isEmpty())
397             mWriter.writeKeyAndValue("type", tile->type());
398 
399         if (!tile->properties().isEmpty())
400             writeProperties(tile->properties());
401 
402         if (!tile->imageSource().isEmpty()) {
403             const QString src = toFileReference(tile->imageSource(), mDir);
404             const QSize tileSize = tile->size();
405             mWriter.writeKeyAndValue("image", src);
406             if (!tileSize.isNull()) {
407                 mWriter.writeKeyAndValue("width", tileSize.width());
408                 mWriter.writeKeyAndValue("height", tileSize.height());
409             }
410         }
411 
412         if (tile->probability() != 1.0)
413             mWriter.writeKeyAndValue("probability", tile->probability());
414 
415         if (ObjectGroup *objectGroup = tile->objectGroup())
416             writeObjectGroup(objectGroup, "objectGroup");
417 
418         if (tile->isAnimated()) {
419             const QVector<Frame> &frames = tile->frames();
420 
421             mWriter.writeStartTable("animation");
422             for (const Frame &frame : frames) {
423                 mWriter.writeStartTable();
424                 mWriter.writeKeyAndValue("tileid", frame.tileId);
425                 mWriter.writeKeyAndValue("duration", frame.duration);
426                 mWriter.writeEndTable();
427             }
428             mWriter.writeEndTable(); // animation
429         }
430 
431         mWriter.writeEndTable(); // tile
432     }
433     mWriter.writeEndTable(); // tiles
434 
435     mWriter.writeEndTable(); // tileset
436 
437     if (!embedded)
438         mWriter.writeEndDocument();
439 }
440 
writeWangSet(const WangSet & wangSet)441 void LuaWriter::writeWangSet(const WangSet &wangSet)
442 {
443     mWriter.writeStartTable();
444 
445     mWriter.writeKeyAndValue("name", wangSet.name());
446     mWriter.writeKeyAndValue("tile", wangSet.imageTileId());
447 
448     writeProperties(wangSet.properties());
449 
450     mWriter.writeStartTable("colors");
451     for (int i = 1; i <= wangSet.colorCount(); ++i) {
452         const WangColor &wangColor = *wangSet.colorAt(i);
453         mWriter.writeStartTable();
454 
455         writeColor("color", wangColor.color());
456         mWriter.writeKeyAndValue("name", wangColor.name());
457         mWriter.writeKeyAndValue("probability", wangColor.probability());
458         mWriter.writeKeyAndValue("tile", wangColor.imageId());
459 
460         writeProperties(wangColor.properties());
461 
462         mWriter.writeEndTable();
463     }
464     mWriter.writeEndTable();
465 
466     mWriter.writeStartTable("wangtiles");
467     const auto wangTiles = wangSet.sortedWangTiles();
468     for (const WangTile &wangTile : wangTiles) {
469         mWriter.writeStartTable();
470 
471         mWriter.writeStartTable("wangid");
472         mWriter.setSuppressNewlines(true);
473         for (int i = 0; i < WangId::NumIndexes; ++i)
474             mWriter.writeValue(wangTile.wangId().indexColor(i));
475         mWriter.writeEndTable();
476         mWriter.setSuppressNewlines(false);
477 
478         mWriter.writeKeyAndValue("tileid", wangTile.tileId());
479 
480         mWriter.writeEndTable();
481     }
482     mWriter.writeEndTable();
483 
484     mWriter.writeEndTable();
485 }
486 
writeLayers(const QList<Layer * > & layers,Map::LayerDataFormat format,int compressionLevel,QSize chunkSize)487 void LuaWriter::writeLayers(const QList<Layer *> &layers,
488                             Map::LayerDataFormat format,
489                             int compressionLevel,
490                             QSize chunkSize)
491 {
492     mWriter.writeStartTable("layers");
493     for (const Layer *layer : layers) {
494         switch (layer->layerType()) {
495         case Layer::TileLayerType:
496             writeTileLayer(static_cast<const TileLayer*>(layer), format, compressionLevel, chunkSize);
497             break;
498         case Layer::ObjectGroupType:
499             writeObjectGroup(static_cast<const ObjectGroup*>(layer));
500             break;
501         case Layer::ImageLayerType:
502             writeImageLayer(static_cast<const ImageLayer*>(layer));
503             break;
504         case Layer::GroupLayerType:
505             writeGroupLayer(static_cast<const GroupLayer*>(layer), format, compressionLevel, chunkSize);
506             break;
507         }
508     }
509     mWriter.writeEndTable();
510 }
511 
writeTileLayer(const TileLayer * tileLayer,Map::LayerDataFormat format,int compressionLevel,QSize chunkSize)512 void LuaWriter::writeTileLayer(const TileLayer *tileLayer,
513                                Map::LayerDataFormat format,
514                                int compressionLevel,
515                                QSize chunkSize)
516 {
517     mWriter.writeStartTable();
518 
519     mWriter.writeKeyAndValue("type", "tilelayer");
520     mWriter.writeKeyAndValue("x", tileLayer->x());
521     mWriter.writeKeyAndValue("y", tileLayer->y());
522     mWriter.writeKeyAndValue("width", tileLayer->width());
523     mWriter.writeKeyAndValue("height", tileLayer->height());
524 
525     writeLayerProperties(tileLayer);
526     writeProperties(tileLayer->properties());
527 
528     switch (format) {
529     case Map::XML:
530     case Map::CSV:
531         mWriter.writeKeyAndValue("encoding", "lua");
532         break;
533 
534     case Map::Base64:
535     case Map::Base64Zlib:
536     case Map::Base64Gzip: {
537         mWriter.writeKeyAndValue("encoding", "base64");
538 
539         if (format == Map::Base64Zlib)
540             mWriter.writeKeyAndValue("compression", "zlib");
541         else if (format == Map::Base64Gzip)
542             mWriter.writeKeyAndValue("compression", "gzip");
543 
544         break;
545     }
546     case Map::Base64Zstandard: {
547         mWriter.writeKeyAndValue("encoding", "base64");
548         mWriter.writeKeyAndValue("compression", "zstd");
549 
550         break;
551     }
552     }
553 
554     if (tileLayer->map()->infinite()) {
555         mWriter.writeStartTable("chunks");
556         const auto chunks = tileLayer->sortedChunksToWrite(chunkSize);
557         for (const QRect &rect : chunks) {
558             mWriter.writeStartTable();
559 
560             mWriter.writeKeyAndValue("x", rect.x());
561             mWriter.setSuppressNewlines(true);
562             mWriter.writeKeyAndValue("y", rect.y());
563             mWriter.writeKeyAndValue("width", rect.width());
564             mWriter.writeKeyAndValue("height", rect.height());
565             mWriter.setSuppressNewlines(false);
566 
567             writeTileLayerData(tileLayer, format, rect, compressionLevel);
568 
569             mWriter.writeEndTable();
570         }
571         mWriter.writeEndTable();
572     } else {
573         writeTileLayerData(tileLayer, format,
574                            QRect(0, 0, tileLayer->width(), tileLayer->height()), compressionLevel);
575     }
576 
577     mWriter.writeEndTable();
578 }
579 
writeTileLayerData(const TileLayer * tileLayer,Map::LayerDataFormat format,QRect bounds,int compressionLevel)580 void LuaWriter::writeTileLayerData(const TileLayer *tileLayer,
581                                    Map::LayerDataFormat format,
582                                    QRect bounds,
583                                    int compressionLevel)
584 {
585     switch (format) {
586     case Map::XML:
587     case Map::CSV:
588         mWriter.writeStartTable("data");
589         for (int y = bounds.top(); y <= bounds.bottom(); ++y) {
590             if (y > bounds.top())
591                 mWriter.prepareNewLine();
592 
593             for (int x = bounds.left(); x <= bounds.right(); ++x)
594                 mWriter.writeValue(mGidMapper.cellToGid(tileLayer->cellAt(x, y)));
595         }
596         mWriter.writeEndTable();
597         break;
598 
599     case Map::Base64:
600     case Map::Base64Zlib:
601     case Map::Base64Gzip:
602     case Map::Base64Zstandard: {
603         QByteArray layerData = mGidMapper.encodeLayerData(*tileLayer, format, bounds, compressionLevel);
604         mWriter.writeKeyAndValue("data", layerData);
605         break;
606     }
607     }
608 }
609 
writeObjectGroup(const ObjectGroup * objectGroup,const char * key)610 void LuaWriter::writeObjectGroup(const ObjectGroup *objectGroup,
611                                  const char *key)
612 {
613     if (key)
614         mWriter.writeStartTable(key);
615     else
616         mWriter.writeStartTable();
617 
618     mWriter.writeKeyAndValue("type", "objectgroup");
619     mWriter.writeKeyAndValue("draworder", drawOrderToString(objectGroup->drawOrder()));
620 
621     writeLayerProperties(objectGroup);
622     writeProperties(objectGroup->properties());
623 
624     mWriter.writeStartTable("objects");
625     for (MapObject *mapObject : objectGroup->objects())
626         writeMapObject(mapObject);
627     mWriter.writeEndTable();
628 
629     mWriter.writeEndTable();
630 }
631 
writeImageLayer(const ImageLayer * imageLayer)632 void LuaWriter::writeImageLayer(const ImageLayer *imageLayer)
633 {
634     mWriter.writeStartTable();
635 
636     mWriter.writeKeyAndValue("type", "imagelayer");
637 
638     const QString rel = toFileReference(imageLayer->imageSource(), mDir);
639     mWriter.writeKeyAndValue("image", rel);
640 
641     if (imageLayer->transparentColor().isValid()) {
642         mWriter.writeKeyAndValue("transparentcolor",
643                                  imageLayer->transparentColor().name());
644     }
645 
646     writeLayerProperties(imageLayer);
647     writeProperties(imageLayer->properties());
648 
649     mWriter.writeEndTable();
650 }
651 
writeGroupLayer(const GroupLayer * groupLayer,Map::LayerDataFormat format,int compressionLevel,QSize chunkSize)652 void LuaWriter::writeGroupLayer(const GroupLayer *groupLayer,
653                                 Map::LayerDataFormat format,
654                                 int compressionLevel,
655                                 QSize chunkSize)
656 {
657     mWriter.writeStartTable();
658 
659     mWriter.writeKeyAndValue("type", "group");
660 
661     writeLayerProperties(groupLayer);
662     writeProperties(groupLayer->properties());
663 
664     writeLayers(groupLayer->layers(), format, compressionLevel, chunkSize);
665 
666     mWriter.writeEndTable();
667 }
668 
toString(MapObject::Shape shape)669 static const char *toString(MapObject::Shape shape)
670 {
671     switch (shape) {
672     case MapObject::Rectangle:
673         return "rectangle";
674     case MapObject::Polygon:
675         return "polygon";
676     case MapObject::Polyline:
677         return "polyline";
678     case MapObject::Ellipse:
679         return "ellipse";
680     case MapObject::Text:
681         return "text";
682     case MapObject::Point:
683         return "point";
684     }
685     return "unknown";
686 }
687 
writeMapObject(const Tiled::MapObject * mapObject)688 void LuaWriter::writeMapObject(const Tiled::MapObject *mapObject)
689 {
690     mWriter.writeStartTable();
691     mWriter.writeKeyAndValue("id", mapObject->id());
692     mWriter.writeKeyAndValue("name", mapObject->name());
693     mWriter.writeKeyAndValue("type", mapObject->type());
694     mWriter.writeKeyAndValue("shape", toString(mapObject->shape()));
695 
696     mWriter.writeKeyAndValue("x", mapObject->x());
697     mWriter.writeKeyAndValue("y", mapObject->y());
698     mWriter.writeKeyAndValue("width", mapObject->width());
699     mWriter.writeKeyAndValue("height", mapObject->height());
700     mWriter.writeKeyAndValue("rotation", mapObject->rotation());
701 
702     if (!mapObject->cell().isEmpty())
703         mWriter.writeKeyAndValue("gid", mGidMapper.cellToGid(mapObject->cell()));
704 
705     mWriter.writeKeyAndValue("visible", mapObject->isVisible());
706 
707     switch (mapObject->shape()) {
708     case MapObject::Rectangle:
709     case MapObject::Ellipse:
710     case MapObject::Point:
711         break;
712     case MapObject::Polygon:
713     case MapObject::Polyline:
714         writePolygon(mapObject);
715         break;
716     case MapObject::Text:
717         writeTextProperties(mapObject);
718         break;
719     }
720 
721     if (const MapObject *base = mapObject->templateObject()) {
722         // Include template properties
723         Properties props = base->properties();
724         mergeProperties(props, mapObject->properties());
725         writeProperties(props);
726     } else {
727         writeProperties(mapObject->properties());
728     }
729 
730     mWriter.writeEndTable();
731 }
732 
writeLayerProperties(const Layer * layer)733 void LuaWriter::writeLayerProperties(const Layer *layer)
734 {
735     if (layer->id() != 0)
736         mWriter.writeKeyAndValue("id", layer->id());
737     mWriter.writeKeyAndValue("name", layer->name());
738     mWriter.writeKeyAndValue("visible", layer->isVisible());
739     mWriter.writeKeyAndValue("opacity", layer->opacity());
740 
741     const QPointF offset = layer->offset();
742     mWriter.writeKeyAndValue("offsetx", offset.x());
743     mWriter.writeKeyAndValue("offsety", offset.y());
744 
745     const QPointF parallaxFactor = layer->parallaxFactor();
746     mWriter.writeKeyAndValue("parallaxx", parallaxFactor.x());
747     mWriter.writeKeyAndValue("parallaxy", parallaxFactor.y());
748 
749     if (layer->tintColor().isValid())
750         writeColor("tintcolor", layer->tintColor());
751 }
752 
writePolygon(const MapObject * mapObject)753 void LuaWriter::writePolygon(const MapObject *mapObject)
754 {
755     if (mapObject->shape() == MapObject::Polygon)
756         mWriter.writeStartTable("polygon");
757     else
758         mWriter.writeStartTable("polyline");
759 
760 #if defined(POLYGON_FORMAT_FULL)
761     /* This format is the easiest to read and understand:
762      *
763      *  {
764      *    { x = 1, y = 1 },
765      *    { x = 2, y = 2 },
766      *    { x = 3, y = 3 },
767      *    ...
768      *  }
769      */
770     for (const QPointF &point : mapObject->polygon()) {
771         mWriter.writeStartTable();
772         mWriter.setSuppressNewlines(true);
773 
774         mWriter.writeKeyAndValue("x", point.x());
775         mWriter.writeKeyAndValue("y", point.y());
776 
777         mWriter.writeEndTable();
778         mWriter.setSuppressNewlines(false);
779     }
780 #elif defined(POLYGON_FORMAT_PAIRS)
781     /* This is an alternative that takes about 25% less memory.
782      *
783      *  {
784      *    { 1, 1 },
785      *    { 2, 2 },
786      *    { 3, 3 },
787      *    ...
788      *  }
789      */
790     for (const QPointF &point : mapObject->polygon()) {
791         mWriter.writeStartTable();
792         mWriter.setSuppressNewlines(true);
793 
794         mWriter.writeValue(point.x());
795         mWriter.writeValue(point.y());
796 
797         mWriter.writeEndTable();
798         mWriter.setSuppressNewlines(false);
799     }
800 #elif defined(POLYGON_FORMAT_OPTIMAL)
801     /* Writing it out in two tables, one for the x coordinates and one for
802      * the y coordinates. It is a compromise between code readability and
803      * performance. This takes the least amount of memory (60% less than
804      * the first approach).
805      *
806      * x = { 1, 2, 3, ... }
807      * y = { 1, 2, 3, ... }
808      */
809 
810     mWriter.writeStartTable("x");
811     mWriter.setSuppressNewlines(true);
812     for (const QPointF &point : mapObject->polygon())
813         mWriter.writeValue(point.x());
814     mWriter.writeEndTable();
815     mWriter.setSuppressNewlines(false);
816 
817     mWriter.writeStartTable("y");
818     mWriter.setSuppressNewlines(true);
819     for (const QPointF &point : mapObject->polygon())
820         mWriter.writeValue(point.y());
821     mWriter.writeEndTable();
822     mWriter.setSuppressNewlines(false);
823 #endif
824 
825     mWriter.writeEndTable();
826 }
827 
writeTextProperties(const MapObject * mapObject)828 void LuaWriter::writeTextProperties(const MapObject *mapObject)
829 {
830     const TextData &textData = mapObject->textData();
831 
832     mWriter.writeKeyAndValue("text", textData.text);
833 
834     if (textData.font.family() != QLatin1String("sans-serif"))
835         mWriter.writeKeyAndValue("fontfamily", textData.font.family());
836     if (textData.font.pixelSize() >= 0 && textData.font.pixelSize() != 16)
837         mWriter.writeKeyAndValue("pixelsize", textData.font.pixelSize());
838     if (textData.wordWrap)
839         mWriter.writeKeyAndValue("wrap", textData.wordWrap);
840     if (textData.color != Qt::black)
841         writeColor("color", textData.color);
842     if (textData.font.bold())
843         mWriter.writeKeyAndValue("bold", textData.font.bold());
844     if (textData.font.italic())
845         mWriter.writeKeyAndValue("italic", textData.font.italic());
846     if (textData.font.underline())
847         mWriter.writeKeyAndValue("underline", textData.font.underline());
848     if (textData.font.strikeOut())
849         mWriter.writeKeyAndValue("strikeout", textData.font.strikeOut());
850     if (!textData.font.kerning())
851         mWriter.writeKeyAndValue("kerning", textData.font.kerning());
852 
853     if (!textData.alignment.testFlag(Qt::AlignLeft)) {
854         if (textData.alignment.testFlag(Qt::AlignHCenter))
855             mWriter.writeKeyAndValue("halign", "center");
856         else if (textData.alignment.testFlag(Qt::AlignRight))
857             mWriter.writeKeyAndValue("halign", "right");
858         else if (textData.alignment.testFlag(Qt::AlignJustify))
859             mWriter.writeKeyAndValue("halign", "justify");
860     }
861 
862     if (!textData.alignment.testFlag(Qt::AlignTop)) {
863         if (textData.alignment.testFlag(Qt::AlignVCenter))
864             mWriter.writeKeyAndValue("valign", "center");
865         else if (textData.alignment.testFlag(Qt::AlignBottom))
866             mWriter.writeKeyAndValue("valign", "bottom");
867     }
868 }
869 
writeColor(const char * name,const QColor & color)870 void LuaWriter::writeColor(const char *name,
871                            const QColor &color)
872 {
873     // Example: backgroundcolor = { 255, 200, 100 }
874     mWriter.writeStartTable(name);
875     mWriter.setSuppressNewlines(true);
876     mWriter.writeValue(color.red());
877     mWriter.writeValue(color.green());
878     mWriter.writeValue(color.blue());
879     if (color.alpha() != 255)
880         mWriter.writeValue(color.alpha());
881     mWriter.writeEndTable();
882     mWriter.setSuppressNewlines(false);
883 }
884 
885 } // namespace Lua
886