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