1 /*
2  * Copyright (C) 2006-2019 Christopho, Solarus - http://www.solarus-games.org
3  *
4  * Solarus is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * Solarus is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17 #include "solarus/core/Debug.h"
18 #include "solarus/entities/GroundInfo.h"
19 #include "solarus/entities/TilesetData.h"
20 #include "solarus/lua/LuaTools.h"
21 #include <ostream>
22 #include <sstream>
23 
24 namespace Solarus {
25 
26 const std::string EnumInfoTraits<PatternScrolling>::pretty_name = "tile scrolling";
27 
28 const EnumInfo<PatternScrolling>::names_type EnumInfoTraits<PatternScrolling>::names = {
29     { PatternScrolling::NONE, "" },
30     { PatternScrolling::PARALLAX, "parallax" },
31     { PatternScrolling::SELF, "self" },
32 };
33 
34 const std::string EnumInfoTraits<PatternRepeatMode>::pretty_name = "tile pattern repeat mode";
35 
36 const EnumInfo<PatternRepeatMode>::names_type EnumInfoTraits<PatternRepeatMode>::names = {
37     { PatternRepeatMode::ALL, "all" },
38     { PatternRepeatMode::HORIZONTAL, "horizontal" },
39     { PatternRepeatMode::VERTICAL, "vertical" },
40     { PatternRepeatMode::NONE, "none" },
41 };
42 
43 /**
44  * \brief Creates a default single-frame tile pattern.
45  */
TilePatternData()46 TilePatternData::TilePatternData() :
47     TilePatternData(Rectangle(0, 0, 16, 16)) {
48 }
49 
50 /**
51  * \brief Creates a single-frame tile pattern.
52  * \param frame Coordinates of the single frame to make.
53  */
TilePatternData(const Rectangle & frame)54 TilePatternData::TilePatternData(const Rectangle& frame) :
55     ground(Ground::TRAVERSABLE),
56     default_layer(0),
57     scrolling(PatternScrolling::NONE),
58     repeat_mode(PatternRepeatMode::ALL),
59     frames(),
60     frame_delay(default_frame_delay),
61     mirror_loop(false) {
62 
63   set_frame(frame);
64 }
65 
66 /**
67  * \brief Returns the kind of ground of this pattern.
68  * \return The ground.
69  */
get_ground() const70 Ground TilePatternData::get_ground() const {
71   return ground;
72 }
73 
74 /**
75  * \brief Sets the kind of ground of this pattern.
76  * \param ground The ground.
77  */
set_ground(Ground ground)78 void TilePatternData::set_ground(Ground ground) {
79   this->ground = ground;
80 }
81 
82 /**
83  * \brief Returns the initial layer when creating a tile with this pattern.
84  * \return The default layer.
85  */
get_default_layer() const86 int TilePatternData::get_default_layer() const {
87   return default_layer;
88 }
89 
90 /**
91  * \brief Sets the initial layer when creating a tile with this pattern.
92  * \param default_layer The default layer.
93  */
set_default_layer(int default_layer)94 void TilePatternData::set_default_layer(int default_layer) {
95   this->default_layer = default_layer;
96 }
97 
98 /**
99  * \brief Returns the kind of scrolling of this pattern.
100  * \return The scrolling property.
101  */
get_scrolling() const102 PatternScrolling TilePatternData::get_scrolling() const {
103   return scrolling;
104 }
105 
106 /**
107  * \brief Sets the kind of scrolling of this pattern.
108  * \param scrolling The scrolling property.
109  */
set_scrolling(PatternScrolling scrolling)110 void TilePatternData::set_scrolling(PatternScrolling scrolling) {
111   this->scrolling = scrolling;
112 }
113 
114 /**
115  * \brief Returns how this pattern is intended to be repeated.
116  */
get_repeat_mode() const117 PatternRepeatMode TilePatternData::get_repeat_mode() const {
118   return repeat_mode;
119 }
120 
121 /**
122  * \brief Sets how this pattern is intended to be repeated.
123  */
set_repeat_mode(PatternRepeatMode repeat_mode)124 void TilePatternData::set_repeat_mode(PatternRepeatMode repeat_mode) {
125   this->repeat_mode = repeat_mode;
126 }
127 
128 /**
129  * \brief Returns the coordinates of each frame of this pattern in the tileset
130  * image file.
131  * \return The coordinates of the frame(s) of this pattern.
132  */
get_frames() const133 const std::vector<Rectangle>& TilePatternData::get_frames() const {
134   return frames;
135 }
136 
137 /**
138  * \brief Sets the coordinates of each frame of this pattern in the tileset
139  * image file.
140  * \param frames The coordinates of the frame(s) of this pattern.
141  */
set_frames(const std::vector<Rectangle> & frames)142 void TilePatternData::set_frames(const std::vector<Rectangle>& frames) {
143 
144   Debug::check_assertion(!frames.empty(), "No frames");
145   this->frames = frames;
146 }
147 
148 /**
149  * \brief Returns whether this is a multi-frame pattern.
150  * \return \c true if the pattern has more than one frame.
151  */
is_multi_frame() const152 bool TilePatternData::is_multi_frame() const {
153   return get_num_frames() > 1;
154 }
155 
156 /**
157  * \brief Returns the number of frames of this pattern.
158  * \return The number of frames.
159  */
get_num_frames() const160 int TilePatternData::get_num_frames() const {
161   return static_cast<int>(frames.size());
162 }
163 
164 /**
165  * \brief Returns the coordinates of this pattern in the tileset image file.
166  *
167  * If the pattern is multi-frame, returns the first frame.
168  *
169  * \return The coordinates of the first frame of this pattern.
170  */
get_frame() const171 Rectangle TilePatternData::get_frame() const {
172 
173   Debug::check_assertion(!frames.empty(), "No pattern frames");
174   return frames[0];
175 }
176 
177 /**
178  * \brief Sets the coordinates of this pattern in the tileset image file.
179  *
180  * The pattern will have a single frame.
181  *
182  * \param frame The coordinates of the single frame of this pattern.
183  */
set_frame(const Rectangle & frame)184 void TilePatternData::set_frame(const Rectangle& frame) {
185   this->frames.clear();
186   this->frames.push_back(frame);
187 }
188 
189 /**
190  * \brief Returns the delay between frames.
191  *
192  * This only has an effect for multi-frame patterns.
193  *
194  * \return The frame delay in milliseconds.
195  */
get_frame_delay() const196 int TilePatternData::get_frame_delay() const {
197   return frame_delay;
198 }
199 
200 /**
201  * \brief Sets the delay between frames.
202  *
203  * This only has an effect for multi-frame patterns.
204  *
205  * \param frame_delay The frame delay in milliseconds.
206  */
set_frame_delay(int frame_delay)207 void TilePatternData::set_frame_delay(int frame_delay) {
208 
209   Debug::check_assertion(frame_delay > 0, "Invalid frame delay");
210   this->frame_delay = frame_delay;
211 }
212 
213 /**
214  * \brief Returns whether the animation plays backwards when looping.
215  * \return \c true if the animation should play backwards.
216  */
is_mirror_loop() const217 bool TilePatternData::is_mirror_loop() const {
218   return mirror_loop;
219 }
220 
221 /**
222  * \brief Sets whether the animation should play backwards when looping.
223  * \param mirror_loop \c true to play the animation backwards.
224  */
set_mirror_loop(bool mirror_loop)225 void TilePatternData::set_mirror_loop(bool mirror_loop) {
226   this->mirror_loop = mirror_loop;
227 }
228 
229 /**
230  * \brief Creates an empty tileset.
231  */
TilesetData()232 TilesetData::TilesetData() :
233     background_color(Color::white),
234     patterns() {
235 
236 }
237 
238 /**
239  * \brief Removes all content of this tileset.
240  */
clear()241 void TilesetData::clear() {
242   background_color = Color::white;
243   patterns.clear();
244   border_sets.clear();
245 }
246 
247 /**
248  * \brief Returns the tileset's background color.
249  * \return The background color.
250  */
get_background_color() const251 Color TilesetData::get_background_color() const {
252   return background_color;
253 }
254 
255 /**
256  * \brief Sets the tileset's background color.
257  * \param background_color The background color.
258  */
set_background_color(const Color & background_color)259 void TilesetData::set_background_color(const Color& background_color) {
260   this->background_color = background_color;
261 }
262 
263 /**
264  * \brief Returns the number of patterns in this tileset.
265  * \return The number of patterns.
266  */
get_num_patterns() const267 int TilesetData::get_num_patterns() const {
268   return patterns.size();
269 }
270 
271 /**
272  * \brief Returns all tile patterns of this tileset.
273  * \return The tile patterns indexed by their id.
274  */
get_patterns() const275 const std::map<std::string, TilePatternData>& TilesetData::get_patterns() const {
276   return patterns;
277 }
278 
279 /**
280  * \brief Returns whether there exists a tile pattern with the specified id.
281  * \param pattern_id The id to test.
282  * \return \c true if a tile pattern exists with this id in the tileset.
283  */
pattern_exists(const std::string & pattern_id) const284 bool TilesetData::pattern_exists(const std::string& pattern_id) const {
285   return patterns.find(pattern_id) != patterns.end();
286 }
287 
288 /**
289  * \brief Returns the pattern with the specified id.
290  * \param pattern_id A tile pattern id.
291  * \return The pattern with this id, or nullptr if it does not exist.
292  * The object remains valid until tile patterns are added or removed.
293  */
get_pattern(const std::string & pattern_id) const294 const TilePatternData* TilesetData::get_pattern(const std::string& pattern_id) const {
295 
296   const auto& it = patterns.find(pattern_id);
297   if (it == patterns.end()) {
298     return nullptr;
299   }
300 
301   return &it->second;
302 }
303 
304 /**
305  * \overload
306  *
307  * Non-const version.
308  */
get_pattern(const std::string & pattern_id)309 TilePatternData* TilesetData::get_pattern(const std::string& pattern_id) {
310 
311   const auto& it = patterns.find(pattern_id);
312   if (it == patterns.end()) {
313     return nullptr;
314   }
315 
316   return &it->second;
317 }
318 
319 /**
320  * \brief Adds a pattern to the tileset.
321  * \param pattern_id Id of the new pattern.
322  * \param pattern The pattern to add.
323  * \return \c true in case of success.
324  */
add_pattern(const std::string & pattern_id,const TilePatternData & pattern)325 bool TilesetData::add_pattern(
326     const std::string& pattern_id, const TilePatternData& pattern) {
327 
328   const auto& result = patterns.emplace(pattern_id, pattern);
329   if (!result.second) {
330     // Insertion failed: the id already exists.
331     return false;
332   }
333 
334   return true;
335 }
336 
337 /**
338  * \brief Removes a pattern from the tileset.
339  * \param pattern_id Id of the pattern to remove.
340  * \return \c true in case of success.
341  */
remove_pattern(const std::string & pattern_id)342 bool TilesetData::remove_pattern(const std::string& pattern_id) {
343 
344   return patterns.erase(pattern_id) > 0;
345 }
346 
347 /**
348  * \brief Changes the id of a pattern in the tileset.
349  * \param old_pattern_id Old id of the pattern.
350  * \param new_pattern_id New id to set.
351  * \return \c true in case of success.
352  * In case of failure, the old pattern is unchanged.
353  */
set_pattern_id(const std::string & old_pattern_id,const std::string & new_pattern_id)354 bool TilesetData::set_pattern_id(
355     const std::string& old_pattern_id, const std::string& new_pattern_id) {
356 
357   if (!pattern_exists(old_pattern_id)) {
358     // No pattern was found with the old id.
359     return false;
360   }
361 
362   if (pattern_exists(new_pattern_id)) {
363     // The new id is already used.
364     return false;
365   }
366 
367   TilePatternData pattern = *get_pattern(old_pattern_id);
368   remove_pattern(old_pattern_id);
369   add_pattern(new_pattern_id, pattern);
370 
371   return true;
372 }
373 
374 /**
375  * \brief Returns the number of border sets in this tileset.
376  * \return The number of border sets.
377  */
get_num_border_sets() const378 int TilesetData::get_num_border_sets() const {
379   return border_sets.size();
380 }
381 
382 /**
383  * \brief Returns all border sets of this tileset.
384  * \return The border sets indexed by their id.
385  */
get_border_sets() const386 const std::map<std::string, BorderSet>& TilesetData::get_border_sets() const {
387   return border_sets;
388 }
389 
390 /**
391  * \brief Returns whether there exists a border set with the specified id.
392  * \param border_set_id The id to test.
393  * \return \c true if a border set exists with this id in the tileset.
394  */
border_set_exists(const std::string & border_set_id) const395 bool TilesetData::border_set_exists(const std::string& border_set_id) const {
396   return border_sets.find(border_set_id) != border_sets.end();
397 }
398 
399 /**
400  * \brief Returns the border set with the specified id.
401  * \param border_set_id A border set id. It must exist.
402  * \return The border_set with this id.
403  * The object remains valid until border sets are added or removed.
404  */
get_border_set(const std::string & border_set_id) const405 const BorderSet& TilesetData::get_border_set(const std::string& border_set_id) const {
406 
407   const auto& it = border_sets.find(border_set_id);
408   Debug::check_assertion(it != border_sets.end(),
409     std::string("No such border set: '") + border_set_id + "'");
410 
411   return it->second;
412 }
413 
414 /**
415  * \brief Returns the border set with the specified id.
416  *
417  * Non-const version.
418  *
419  * \param border_set_id A border set id. It must exist.
420  * \return The border_set with this id.
421  * The object remains valid until border sets are added or removed.
422  */
get_border_set(const std::string & border_set_id)423 BorderSet& TilesetData::get_border_set(const std::string& border_set_id) {
424 
425   const auto& it = border_sets.find(border_set_id);
426   Debug::check_assertion(it != border_sets.end(),
427     std::string("No such border set: '") + border_set_id + "'");
428 
429   return it->second;
430 }
431 
432 /**
433  * \brief Adds a border set to the tileset.
434  * \param border_set_id Id of the new border set.
435  * \param border_set The border set to add.
436  * \return \c true in case of success.
437  */
add_border_set(const std::string & border_set_id,const BorderSet & border_set)438 bool TilesetData::add_border_set(
439     const std::string& border_set_id, const BorderSet& border_set) {
440 
441   const auto& result = border_sets.emplace(border_set_id, border_set);
442   if (!result.second) {
443     // Insertion failed: the id already exists.
444     return false;
445   }
446 
447   return true;
448 }
449 
450 /**
451  * \brief Removes a border set from the tileset.
452  * \param border_set_id Id of the border set to remove.
453  * \return \c true in case of success.
454  */
remove_border_set(const std::string & border_set_id)455 bool TilesetData::remove_border_set(const std::string& border_set_id) {
456 
457   return border_sets.erase(border_set_id) > 0;
458 }
459 
460 /**
461  * \brief Changes the id of a border set in the tileset.
462  * \param old_border_set_id Old id of the border set.
463  * \param new_border_set_id New id to set.
464  * \return \c true in case of success.
465  * In case of failure, the old border set is unchanged.
466  */
set_border_set_id(const std::string & old_border_set_id,const std::string & new_border_set_id)467 bool TilesetData::set_border_set_id(
468     const std::string& old_border_set_id, const std::string& new_border_set_id) {
469 
470   if (!border_set_exists(old_border_set_id)) {
471     // No border set was found with the old id.
472     return false;
473   }
474 
475   if (border_set_exists(new_border_set_id)) {
476     // The new id is already used.
477     return false;
478   }
479 
480   BorderSet border_set = get_border_set(old_border_set_id);
481   remove_border_set(old_border_set_id);
482   add_border_set(new_border_set_id, border_set);
483 
484   return true;
485 }
486 
487 namespace {
488 
489 /**
490  * \brief Function called by Lua to set the background color of the tileset.
491  *
492  * - Argument 1 (table): background color (must be an array of 3 integers).
493  *
494  * \param l The Lua context that is calling this function.
495  * \return Number of values to return to Lua.
496  */
l_background_color(lua_State * l)497 int l_background_color(lua_State* l) {
498 
499   return LuaTools::exception_boundary_handle(l, [&] {
500     lua_getfield(l, LUA_REGISTRYINDEX, "tileset");
501     TilesetData& tileset = *static_cast<TilesetData*>(lua_touserdata(l, -1));
502     lua_pop(l, 1);
503 
504     const Color& background_color = LuaTools::check_color(l, 1);
505 
506     tileset.set_background_color(background_color);
507 
508     return 0;
509   });
510 }
511 
512 /**
513  * \brief Function called by Lua to add a tile pattern to the tileset.
514  *
515  * - Argument 1 (table): A table describing the tile pattern to create.
516  *
517  * \param l The Lua context that is calling this function.
518  * \return Number of values to return to Lua.
519  */
l_tile_pattern(lua_State * l)520 int l_tile_pattern(lua_State* l) {
521 
522   return LuaTools::exception_boundary_handle(l, [&] {
523     lua_getfield(l, LUA_REGISTRYINDEX, "tileset");
524     TilesetData& tileset_data = *static_cast<TilesetData*>(lua_touserdata(l, -1));
525     lua_pop(l, 1);
526 
527     TilePatternData pattern_data;
528 
529     const std::string& id = LuaTools::check_string_field(l, 1, "id");
530 
531     const Ground ground = LuaTools::check_enum_field<Ground>(
532         l, 1, "ground"
533     );
534     pattern_data.set_ground(ground);
535 
536     const int default_layer = LuaTools::check_int_field(
537         l, 1, "default_layer"
538     );
539     pattern_data.set_default_layer(default_layer);
540 
541     const PatternScrolling scrolling = LuaTools::opt_enum_field<PatternScrolling>(
542         l, 1, "scrolling", PatternScrolling::NONE
543     );
544     pattern_data.set_scrolling(scrolling);
545 
546     const PatternRepeatMode repeat_mode = LuaTools::opt_enum_field<PatternRepeatMode>(
547         l, 1, "repeat_mode", PatternRepeatMode::ALL
548     );
549     pattern_data.set_repeat_mode(repeat_mode);
550 
551     const int frame_delay = LuaTools::opt_int_field(
552         l, 1, "frame_delay", TilePatternData::default_frame_delay
553     );
554     pattern_data.set_frame_delay(frame_delay);
555 
556     std::vector<int> x;
557     lua_settop(l, 1);
558     lua_getfield(l, 1, "x");
559     if (lua_isnumber(l, 2)) {
560       // Single frame.
561       x.push_back(LuaTools::check_int(l, 2));
562     }
563     else if (lua_istable(l, 2)) {
564       // Multi-frame.
565       lua_pushnil(l);
566       while (lua_next(l, 2) != 0) {
567         x.push_back(LuaTools::check_int(l, 4));
568         lua_pop(l, 1);
569       }
570     }
571     else {
572       LuaTools::type_error(l, 2, "number or table");
573     }
574     lua_pop(l, 1);
575     Debug::check_assertion(lua_gettop(l) == 1, "Invalid stack when parsing tile pattern");
576 
577     std::vector<int> y;
578     lua_getfield(l, 1, "y");
579     if (lua_isnumber(l, 2)) {
580       // Single frame.
581       y.push_back(LuaTools::check_int(l, 2));
582     }
583     else if (lua_istable(l, 2)) {
584       // Multi-frame.
585       lua_pushnil(l);
586       while (lua_next(l, 2) != 0) {
587         y.push_back(LuaTools::check_int(l, 4));
588         lua_pop(l, 1);
589       }
590     }
591     else {
592       LuaTools::type_error(l, 2, "number or table");
593     }
594     lua_pop(l, 1);
595     Debug::check_assertion(lua_gettop(l) == 1, "Invalid stack when parsing tile pattern");
596 
597     if (x.size() != y.size()) {
598       LuaTools::arg_error(l, 1, "The length of x and y must match");
599     }
600     if (x.size() == 0) {
601       LuaTools::arg_error(l, 1, "Missing x and y frame coordinates");
602     }
603 
604     const int width = LuaTools::check_int_field(l, 1, "width");
605     const int height = LuaTools::check_int_field(l, 1, "height");
606 
607     std::vector<Rectangle> frames(x.size(), Rectangle(0, 0, width, height));
608     for (int i = 0; i < static_cast<int>(x.size()); ++i) {
609       frames[i].set_x(x[i]);
610       frames[i].set_y(y[i]);
611     }
612 
613     bool mirror_loop = LuaTools::opt_boolean_field(
614         l, 1, "mirror_loop", false
615     );
616     // Detect 1.5 legacy mirror loop format
617     // and replace it by the mirror_loop boolean.
618     if (frames.size() == 4 && frames[1] == frames[3]) {
619       frames.pop_back();
620       mirror_loop = true;
621     }
622     pattern_data.set_mirror_loop(mirror_loop);
623 
624     pattern_data.set_frames(frames);
625 
626     tileset_data.add_pattern(id, pattern_data);
627 
628     return 0;
629   });
630 }
631 
632 /**
633  * \brief Function called by Lua to add a border set to the tileset.
634  *
635  * - Argument 1 (table): A table describing the border set to create.
636  *
637  * \param l The Lua context that is calling this function.
638  * \return Number of values to return to Lua.
639  */
l_border_set(lua_State * l)640 int l_border_set(lua_State* l) {
641 
642   return LuaTools::exception_boundary_handle(l, [&] {
643     lua_getfield(l, LUA_REGISTRYINDEX, "tileset");
644     TilesetData& tileset_data = *static_cast<TilesetData*>(lua_touserdata(l, -1));
645     lua_pop(l, 1);
646 
647     BorderSet border_set;
648 
649     const std::string& id = LuaTools::check_string_field(l, 1, "id");
650 
651     bool inner = LuaTools::opt_boolean_field(l, 1, "inner", false);
652     border_set.set_inner(inner);
653 
654     std::map<BorderKind, std::string> patterns;
655     for (int i = 0; i < 12; ++i) {
656       std::ostringstream oss;
657       oss << "pattern_" << i;
658       const std::string& pattern_id = LuaTools::opt_string_field(l, 1, oss.str(), "");
659       if (!pattern_id.empty()) {
660         BorderKind border_kind = static_cast<BorderKind>(i);
661         border_set.set_pattern(border_kind, pattern_id);
662       }
663     }
664 
665     tileset_data.add_border_set(id, border_set);
666 
667     return 0;
668   });
669 }
670 
671 }  // Anonymous namespace.
672 
673 /**
674  * \copydoc LuaData::import_from_lua
675  */
import_from_lua(lua_State * l)676 bool TilesetData::import_from_lua(lua_State* l) {
677 
678   clear();
679   lua_pushlightuserdata(l, this);
680   lua_setfield(l, LUA_REGISTRYINDEX, "tileset");
681   lua_register(l, "background_color", l_background_color);
682   lua_register(l, "tile_pattern", l_tile_pattern);
683   lua_register(l, "border_set", l_border_set);
684   if (lua_pcall(l, 0, 0, 0) != 0) {
685     Debug::error(std::string("Failed to load tileset: ") + lua_tostring(l, -1));
686     lua_pop(l, 1);
687     return false;
688   }
689 
690   return true;
691 }
692 
693 /**
694  * \copydoc LuaData::export_to_lua
695  */
export_to_lua(std::ostream & out) const696 bool TilesetData::export_to_lua(std::ostream& out) const {
697 
698   // Background color.
699   uint8_t r, g, b, a;
700   background_color.get_components(r, g, b, a);
701   out << "background_color{ "
702       << static_cast<int>(r)
703       << ", "
704       << static_cast<int>(g)
705       << ", "
706       << static_cast<int>(b)
707       << " }\n";
708 
709   // Tile patterns.
710   for (const auto& kvp : patterns) {
711     const std::string& id = kvp.first;
712     const TilePatternData& pattern = kvp.second;
713 
714     const Rectangle& first_frame = pattern.get_frame();
715     int width = first_frame.get_width();
716     int height = first_frame.get_height();
717     std::ostringstream x;
718     std::ostringstream y;
719     if (!pattern.is_multi_frame()) {
720       x << first_frame.get_x();
721       y << first_frame.get_y();
722     }
723     else {
724       x << "{ ";
725       y << "{ ";
726       bool first = true;
727       for (const Rectangle& frame : pattern.get_frames()) {
728         if (first) {
729           first = false;
730         }
731         else {
732           x << ", ";
733           y << ", ";
734         }
735         x << frame.get_x();
736         y << frame.get_y();
737       }
738       x << " }";
739       y << " }";
740     }
741 
742     const std::string& ground_name = enum_to_name(pattern.get_ground());
743     int default_layer = static_cast<int>(pattern.get_default_layer());
744 
745     out << "tile_pattern{\n"
746         << "  id = \"" << escape_string(id) << "\",\n"
747         << "  ground = \"" << ground_name << "\",\n"
748         << "  default_layer = " << default_layer << ",\n"
749         << "  x = " << x.str() << ",\n"
750         << "  y = " << y.str() << ",\n"
751         << "  width = " << width << ",\n"
752         << "  height = " << height << ",\n";
753     if (pattern.is_multi_frame()) {
754       out << "  frame_delay = " << pattern.get_frame_delay() << ",\n";
755       if (pattern.is_mirror_loop()) {
756         out << "  mirror_loop = true,\n";
757       }
758     }
759     if (pattern.get_scrolling() != PatternScrolling::NONE) {
760       const std::string& scrolling_name = enum_to_name(pattern.get_scrolling());
761       out << "  scrolling = \"" << scrolling_name << "\",\n";
762     }
763     if (pattern.get_repeat_mode() != PatternRepeatMode::ALL) {
764       const std::string& repeat_mode_name = enum_to_name(pattern.get_repeat_mode());
765       out << "  repeat_mode = \"" << repeat_mode_name << "\",\n";
766     }
767     out << "}\n\n";
768   }
769 
770   // Border sets.
771   for (const auto& kvp : border_sets) {
772     const std::string& id = kvp.first;
773     const BorderSet& border_set = kvp.second;
774 
775     out << "border_set{\n"
776         << "  id = \"" << escape_string(id) << "\",\n";
777     if (border_set.is_inner()) {
778       out << "  inner = true,\n";
779     }
780     for (int i = 0; i < 12; ++i) {
781       BorderKind border_kind = static_cast<BorderKind>(i);
782       const std::string& pattern_id = border_set.get_pattern(border_kind);
783       if (!pattern_id.empty()) {
784         out << "  pattern_" << i << " = \"" << pattern_id << "\",\n";
785       }
786     }
787 
788     out << "}\n\n";
789   }
790 
791   return true;
792 }
793 
794 }
795