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