Areas no longer have an author string.
[Tsunagari.git] / src / area-tmx.cpp
blob7f3f1763fabf93344896c29ea37c71e9bf882002
1 /*********************************
2 ** Tsunagari Tile Engine **
3 ** area-tmx.cpp **
4 ** Copyright 2011-2012 OmegaSDG **
5 *********************************/
7 // **********
8 // Permission is hereby granted, free of charge, to any person obtaining a copy
9 // of this software and associated documentation files (the "Software"), to
10 // deal in the Software without restriction, including without limitation the
11 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12 // sell copies of the Software, and to permit persons to whom the Software is
13 // furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24 // IN THE SOFTWARE.
25 // **********
27 #include <math.h>
28 #include <vector>
30 #include <boost/foreach.hpp>
31 #include <boost/shared_ptr.hpp>
32 #include <Gosu/Graphics.hpp>
33 #include <Gosu/Image.hpp>
34 #include <Gosu/Math.hpp>
35 #include <Gosu/Timing.hpp>
37 #include "area-tmx.h"
38 #include "entity.h"
39 #include "log.h"
40 #include "python.h"
41 #include "reader.h"
42 #include "string.h"
43 #include "tile.h"
44 #include "window.h"
45 #include "world.h"
47 #ifdef _WIN32
48 #include "os-windows.h"
49 #endif
51 #define ASSERT(x) if (!(x)) { return false; }
53 /* NOTE: In the TMX map format used by Tiled, tileset tiles start counting
54 their Y-positions from 0, while layer tiles start counting from 1. I
55 can't imagine why the author did this, but we have to take it into
56 account.
59 AreaTMX::AreaTMX(Viewport* view,
60 Player* player,
61 const std::string& descriptor)
62 : Area(view, player, descriptor)
64 // Add TileType #0. Not used, but Tiled's gids start from 1.
65 gids.push_back(NULL);
68 AreaTMX::~AreaTMX()
72 bool AreaTMX::init()
74 return processDescriptor();
78 void AreaTMX::allocateMapLayer()
80 map.push_back(grid_t(dim.y, row_t(dim.x)));
81 grid_t& grid = map[dim.z];
82 for (int y = 0; y < dim.y; y++) {
83 row_t& row = grid[y];
84 for (int x = 0; x < dim.x; x++) {
85 Tile& tile = row[x];
86 new (&tile) Tile(this, x, y, dim.z);
89 dim.z++;
92 bool AreaTMX::processDescriptor()
94 XMLRef doc;
95 XMLNode root;
97 ASSERT(doc = Reader::getXMLDoc(descriptor, "dtd/area.dtd"));
98 ASSERT(root = doc->root()); // <map>
100 ASSERT(root.intAttr("width", &dim.x));
101 ASSERT(root.intAttr("height", &dim.y));
102 dim.z = 0;
104 for (XMLNode child = root.childrenNode(); child; child = child.next()) {
105 if (child.is("properties")) {
106 ASSERT(processMapProperties(child));
108 else if (child.is("tileset")) {
109 ASSERT(processTileSet(child));
111 else if (child.is("layer")) {
112 ASSERT(processLayer(child));
114 else if (child.is("objectgroup")) {
115 ASSERT(processObjectGroup(child));
119 return true;
122 bool AreaTMX::processMapProperties(XMLNode node)
126 <properties>
127 <property name="name" value="Wooded AreaTMX"/>
128 <property name="intro_music" value="arrive.ogg"/>
129 <property name="main_music" value="wind.ogg"/>
130 <property name="on_load" value="wood_setup.py"/>
131 <property name="on_focus" value="wood_focus.py"/>
132 <property name="on_tick" value="wood_tick.py"/>
133 <property name="on_turn" value="wood_turn.py"/>
134 <property name="loop" value="xy"/>
135 <property name="color_overlay" value="255,255,255,127"/>
136 </properties>
138 for (XMLNode child = node.childrenNode(); child; child = child.next()) {
139 std::string name = child.attr("name");
140 std::string value = child.attr("value");
141 if (name == "name")
142 this->name = value;
143 else if (name == "intro_music") {
144 musicIntro = value;
146 else if (name == "main_music") {
147 musicLoop = value;
149 else if (name == "on_load") {
150 std::string filename = value;
151 ScriptInst script(filename);
152 if (!script.validate(descriptor))
153 return false;
154 loadScript = filename;
156 else if (name == "on_focus") {
157 std::string filename = value;
158 ScriptInst script(filename);
159 if (!script.validate(descriptor))
160 return false;
161 focusScript = filename;
163 else if (name == "on_tick") {
164 std::string filename = value;
165 ScriptInst script(filename);
166 if (!script.validate(descriptor))
167 return false;
168 tickScript = filename;
170 else if (name == "on_turn") {
171 std::string filename = value;
172 ScriptInst script(filename);
173 if (!script.validate(descriptor))
174 return false;
175 turnScript = filename;
177 else if (name == "loop") {
178 loopX = value.find('x') != std::string::npos;
179 loopY = value.find('y') != std::string::npos;
181 else if (name == "color_overlay") {
182 Gosu::Color::Channel r, g, b, a;
183 ASSERT(parseRGBA(value, &r, &g, &b, &a));
184 colorOverlay = Gosu::Color(a, r, g, b);
188 return true;
191 bool AreaTMX::processTileSet(XMLNode node)
195 <tileset firstgid="1" name="tiles.sheet" tilewidth="64" tileheight="64">
196 <image source="tiles.sheet" width="256" height="256"/>
197 <tile id="14">
199 </tile>
200 </tileset>
203 XMLRef doc;
204 std::string source;
206 TileSet* set = NULL;
207 TiledImageRef img;
208 int tilex, tiley;
209 int firstGid;
211 // Read firstgid from original node.
212 ASSERT(node.intAttr("firstgid", &firstGid));
214 // If this node is just a reference to an external TSX file, load it
215 // and process the root tileset element of the TSX, instead.
216 source = node.attr("source");
217 if (source.size()) {
218 if (!(doc = Reader::getXMLDoc(source, "dtd/tsx.dtd"))) {
219 Log::err(descriptor, source + ": failed to load valid TSX file");
220 return false;
222 ASSERT(node = doc->root()); // <tileset>
225 ASSERT(node.intAttr("tilewidth", &tilex));
226 ASSERT(node.intAttr("tileheight", &tiley));
228 if (tileDim && tileDim != ivec2(tilex, tiley)) {
229 Log::err(descriptor,
230 "<tileset>'s width/height contradict earlier <layer>");
231 return false;
233 tileDim = ivec2(tilex, tiley);
235 for (XMLNode child = node.childrenNode(); child; child = child.next()) {
236 if (child.is("image")) {
237 int pixelw, pixelh;
238 ASSERT(child.intAttr("width", &pixelw) &&
239 child.intAttr("height", &pixelh));
240 int width = pixelw / tileDim.x;
241 int height = pixelh / tileDim.y;
243 std::string source = child.attr("source");
244 tileSets[source] = TileSet(width, height);
245 set = &tileSets[source];
247 // Load tileset image.
248 img = Reader::getTiledImage(source, tilex, tiley);
249 if (!img) {
250 Log::err(descriptor, "tileset image not found");
251 return false;
254 // Initialize "vanilla" tile type array.
255 for (size_t i = 0; i < img->size(); i++) {
256 ImageRef& tileImg = (*img.get())[i];
257 TileType* type = new TileType(tileImg);
258 set->add(type);
259 gids.push_back(type);
262 else if (child.is("tile")) {
263 // Handle an explicitly declared "non-vanilla" type.
265 if (!img) {
266 Log::err(descriptor,
267 "Tile type processed before tileset image loaded");
268 return false;
271 // "id" is 0-based index of a tile in the current
272 // tileset, if the tileset were a flat array.
273 int id;
274 ASSERT(child.intAttr("id", &id));
276 if (id < 0 || (int)img->size() <= id) {
277 Log::err(descriptor, "tile type id is invalid");
278 return false;
281 // Initialize a default TileType, we'll build on that.
282 TileType* type = new TileType((*img.get())[id]);
283 ASSERT(processTileType(child, *type, img, id));
285 // "gid" is the global area-wide id of the tile.
286 size_t gid = id + firstGid;
287 delete gids[gid]; // "vanilla" type
288 gids[gid] = type;
289 set->set(id, type);
293 return true;
296 bool AreaTMX::processTileType(XMLNode node, TileType& type,
297 TiledImageRef& img, int id)
301 <tile id="8">
302 <properties>
303 <property name="flags" value="nowalk"/>
304 <property name="onEnter" value="skid();speed(2)"/>
305 <property name="onLeave" value="undo()"/>
306 <property name="onUse" value="undo()"/>
307 </properties>
308 </tile>
309 <tile id="14">
310 <properties>
311 <property name="frames" value="1,2,3,4"/>
312 <property name="speed" value="2"/>
313 </properties>
314 </tile>
317 // The id has already been handled by processTileSet, so we don't have
318 // to worry about it.
320 // If a Tile is animated, it needs both member frames and a speed.
321 std::vector<ImageRef> framesvec;
322 int cycles = ANIM_INFINITE_CYCLES;
323 int frameLen = -1;
325 XMLNode child = node.childrenNode(); // <properties>
326 for (child = child.childrenNode(); child; child = child.next()) {
327 // Each <property>...
328 std::string name = child.attr("name");
329 std::string value = child.attr("value");
330 if (name == "flags") {
331 ASSERT(splitTileFlags(value, &type.flags));
333 else if (name == "on_enter") {
334 std::string filename = value;
335 ScriptInst script(filename);
336 if (!script.validate(descriptor))
337 return false;
338 type.enterScript = filename;
340 else if (name == "on_leave") {
341 std::string filename = value;
342 ScriptInst script(filename);
343 if (!script.validate(descriptor))
344 return false;
345 type.leaveScript = filename;
347 else if (name == "on_use") {
348 std::string filename = value;
349 ScriptInst script(filename);
350 if (!script.validate(descriptor))
351 return false;
352 type.useScript = filename;
354 else if (name == "frames") {
355 std::string memtemp;
356 std::vector<std::string> frames;
357 std::vector<std::string>::iterator it;
358 memtemp = value;
359 frames = splitStr(memtemp, ",");
361 // Make sure the first member is this tile.
362 if (atoi(frames[0].c_str()) != id) {
363 Log::err(descriptor, "first member of tile"
364 " id " + itostr(id) +
365 " animation must be itself.");
366 return false;
369 // Add frames to our animation.
370 // We already have one from TileType's constructor.
371 for (it = frames.begin(); it < frames.end(); it++) {
372 int idx = atoi(it->c_str());
373 if (idx < 0 || (int)img->size() <= idx) {
374 Log::err(descriptor, "frame index out "
375 "of range for animated tile");
376 return false;
378 framesvec.push_back((*img.get())[idx]);
381 else if (name == "speed") {
382 double hertz;
383 ASSERT(child.doubleAttr("value", &hertz));
384 frameLen = (int)(1000.0/hertz);
386 else if (name == "cycles") {
387 ASSERT(child.intAttr("value", &cycles));
391 if (framesvec.size() || frameLen != -1) {
392 if (framesvec.empty() || frameLen == -1) {
393 Log::err(descriptor, "tile type must either have both "
394 "frames and speed or none");
395 return false;
397 // Add 'now' to Animation constructor??
398 time_t now = World::instance()->time();
399 type.anim = Animation(framesvec, frameLen);
400 type.anim.startOver(now, cycles);
403 return true;
406 bool AreaTMX::processLayer(XMLNode node)
410 <layer name="Tiles0" width="5" height="5">
411 <properties>
413 </properties>
414 <data>
415 <tile gid="9"/>
416 <tile gid="9"/>
417 <tile gid="9"/>
419 <tile gid="3"/>
420 <tile gid="9"/>
421 <tile gid="9"/>
422 </data>
423 </layer>
426 int x, y;
427 double depth;
428 ASSERT(node.intAttr("width", &x));
429 ASSERT(node.intAttr("height", &y));
431 if (dim.x != x || dim.y != y) {
432 Log::err(descriptor, "layer x,y size != map x,y size");
433 return false;
436 allocateMapLayer();
438 for (XMLNode child = node.childrenNode(); child; child = child.next()) {
439 if (child.is("properties")) {
440 ASSERT(processLayerProperties(child, &depth));
442 else if (child.is("data")) {
443 ASSERT(processLayerData(child, dim.z - 1));
447 return true;
450 bool AreaTMX::processLayerProperties(XMLNode node, double* depth)
454 <properties>
455 <property name="layer" value="0"/>
456 </properties>
459 bool layerFound = false;
461 for (XMLNode child = node.childrenNode(); child; child = child.next()) {
462 std::string name = child.attr("name");
463 std::string value = child.attr("value");
464 if (name == "layer") {
465 layerFound = true;
466 ASSERT(child.doubleAttr("value", depth));
467 if (depth2idx.find(*depth) != depth2idx.end()) {
468 Log::err(descriptor,
469 "depth used multiple times");
470 return false;
473 depth2idx[*depth] = dim.z - 1;
474 idx2depth.push_back(*depth);
475 // Effectively idx2depth[dim.z - 1] = depth;
479 if (!layerFound)
480 Log::err(descriptor, "<layer> must have layer property");
481 return layerFound;
484 bool AreaTMX::processLayerData(XMLNode node, int z)
488 <data>
489 <tile gid="9"/>
490 <tile gid="9"/>
491 <tile gid="9"/>
493 <tile gid="3"/>
494 <tile gid="9"/>
495 <tile gid="9"/>
496 </data>
499 int x = 0, y = 0;
501 for (XMLNode child = node.childrenNode(); child; child = child.next()) {
502 if (child.is("tile")) {
503 int gid;
504 ASSERT(child.intAttr("gid", &gid));
506 if (gid < 0 || (int)gids.size() <= gid) {
507 Log::err(descriptor, "invalid tile gid");
508 return false;
511 // A gid of zero means there is no tile at this
512 // position on this layer.
513 if (gid > 0) {
514 TileType* type = gids[gid];
515 Tile& tile = map[z][y][x];
516 type->allOfType.push_back(&tile);
517 tile.parent = type;
520 if (++x == dim.x) {
521 x = 0;
522 y++;
527 return true;
530 bool AreaTMX::processObjectGroup(XMLNode node)
534 <objectgroup name="Prop0" width="5" height="5">
535 <properties>
536 <property name="layer" value="0.0"/>
537 </properties>
538 <object name="tile2" gid="7" x="64" y="320">
539 <properties>
540 <property name="onEnter" value="speed(0.5)"/>
541 <property name="onLeave" value="undo()"/>
542 <property name="onUse" value="undo()"/>
543 <property name="exit" value="grassfield.area,1,1,0"/>
544 <property name="flags" value="npc_nowalk"/>
545 </properties>
546 </object>
547 </objectgroup>
550 double invalid = (double)NAN; // Not a number.
551 int x, y;
552 ASSERT(node.intAttr("width", &x));
553 ASSERT(node.intAttr("height", &y));
555 double depth = invalid;
557 if (dim.x != x || dim.y != y) {
558 Log::err(descriptor, "objectgroup x,y size != map x,y size");
559 return false;
562 for (XMLNode child = node.childrenNode(); child; child = child.next()) {
563 if (child.is("properties")) {
564 ASSERT(processObjectGroupProperties(child, &depth));
566 else if (child.is("object")) {
567 ASSERT(depth != invalid);
568 int z = depth2idx[depth];
569 ASSERT(processObject(child, z));
573 return true;
576 bool AreaTMX::processObjectGroupProperties(XMLNode node, double* depth)
580 <properties>
581 <property name="layer" value="0.0"/>
582 </properties>
584 bool layerFound = false;
586 for (XMLNode child = node.childrenNode(); child; child = child.next()) {
587 std::string name = child.attr("name");
588 std::string value = child.attr("value");
589 if (name == "layer") {
590 layerFound = true;
591 ASSERT(child.doubleAttr("value", depth));
592 if (depth2idx.find(*depth) == depth2idx.end()) {
593 allocateMapLayer();
594 depth2idx[*depth] = dim.z - 1;
595 idx2depth.push_back(*depth);
596 // Effectively idx2depth[dim.z - 1] = depth;
601 if (!layerFound)
602 Log::err(descriptor, "<objectgroup> must have layer property");
603 return layerFound;
606 bool AreaTMX::processObject(XMLNode node, int z)
610 <object name="tile2" gid="7" x="64" y="320">
611 <properties>
612 <property name="onEnter" value="speed(0.5)"/>
613 <property name="onLeave" value="undo()"/>
614 <property name="onUse" value="undo()"/>
615 <property name="exit" value="grassfield.area,1,1,0"/>
616 <property name="flags" value="npc_nowalk"/>
617 </properties>
618 </object>
619 <object name="foo" x="0" y="0" width="64" height="64">
621 </object>
624 // Gather object properties now. Assign them to tiles later.
625 bool wwide[5], hwide[5]; /* wide exit in dimensions: width, height */
627 ScriptInst enterScript, leaveScript, useScript;
628 boost::scoped_ptr<Exit> exit[5];
629 boost::optional<double> layermods[5];
630 unsigned flags = 0x0;
632 XMLNode child = node.childrenNode(); // <properties>
633 if (!child) {
634 // Empty <object> element. Odd, but acceptable.
635 return true;
637 for (child = child.childrenNode(); child; child = child.next()) {
638 // Each <property>...
639 std::string name = child.attr("name");
640 std::string value = child.attr("value");
641 if (name == "flags") {
642 ASSERT(splitTileFlags(value, &flags));
644 else if (name == "on_enter") {
645 std::string filename = value;
646 ScriptInst script(filename);
647 if (!script.validate(descriptor))
648 return false;
649 enterScript = script;
651 else if (name == "on_leave") {
652 std::string filename = value;
653 ScriptInst script(filename);
654 if (!script.validate(descriptor))
655 return false;
656 leaveScript = script;
658 else if (name == "on_use") {
659 std::string filename = value;
660 ScriptInst script(filename);
661 if (!script.validate(descriptor))
662 return false;
663 useScript = script;
665 else if (name == "exit") {
666 exit[EXIT_NORMAL].reset(new Exit);
667 ASSERT(parseExit(value, exit[EXIT_NORMAL].get(), &wwide[EXIT_NORMAL], &hwide[EXIT_NORMAL]));
668 flags |= TILE_NOWALK_NPC;
670 else if (name == "exit:up") {
671 exit[EXIT_UP].reset(new Exit);
672 ASSERT(parseExit(value, exit[EXIT_UP].get(), &wwide[EXIT_UP], &hwide[EXIT_UP]));
674 else if (name == "exit:down") {
675 exit[EXIT_DOWN].reset(new Exit);
676 ASSERT(parseExit(value, exit[EXIT_DOWN].get(), &wwide[EXIT_DOWN], &hwide[EXIT_DOWN]));
678 else if (name == "exit:left") {
679 exit[EXIT_LEFT].reset(new Exit);
680 ASSERT(parseExit(value, exit[EXIT_LEFT].get(), &wwide[EXIT_LEFT], &hwide[EXIT_LEFT]));
682 else if (name == "exit:right") {
683 exit[EXIT_RIGHT].reset(new Exit);
684 ASSERT(parseExit(value, exit[EXIT_RIGHT].get(), &wwide[EXIT_RIGHT], &hwide[EXIT_RIGHT]));
686 else if (name == "layermod") {
687 double mod;
688 ASSERT(child.doubleAttr("value", &mod));
689 layermods[EXIT_NORMAL].reset(mod);
690 flags |= TILE_NOWALK_NPC;
692 else if (name == "layermod:up") {
693 double mod;
694 ASSERT(child.doubleAttr("value", &mod));
695 layermods[EXIT_UP].reset(mod);
697 else if (name == "layermod:down") {
698 double mod;
699 ASSERT(child.doubleAttr("value", &mod));
700 layermods[EXIT_DOWN].reset(mod);
702 else if (name == "layermod:left") {
703 double mod;
704 ASSERT(child.doubleAttr("value", &mod));
705 layermods[EXIT_LEFT].reset(mod);
707 else if (name == "layermod:right") {
708 double mod;
709 ASSERT(child.doubleAttr("value", &mod));
710 layermods[EXIT_RIGHT].reset(mod);
714 // Apply these properties directly to one or more tiles in a rectangle
715 // of the map. We don't keep an intermediary "object" object lying
716 // around.
717 int x, y, w, h;
718 ASSERT(node.intAttr("x", &x));
719 ASSERT(node.intAttr("y", &y));
720 x /= tileDim.x;
721 y /= tileDim.y;
723 if (node.hasAttr("gid")) {
724 // This is one of Tiled's "Tile Objects". It is one tile wide
725 // and high.
727 // Bug in tiled. The y is off by one. The author of the format
728 // knows about this, but it will not change.
729 y = y - 1;
730 w = 1;
731 h = 1;
733 // We don't actually use the object gid. It is supposed to
734 // indicate which tile our object is rendered as, but for
735 // Tsunagari, tile objects are always transparent and reveal
736 // the tile below.
738 else {
739 // This is one of Tiled's "Objects". It has a width and height.
740 ASSERT(node.intAttr("width", &w));
741 ASSERT(node.intAttr("height", &h));
742 w /= tileDim.x;
743 h /= tileDim.y;
746 // We know which Tiles are being talked about now... yay
747 for (int Y = y; Y < y + h; Y++) {
748 for (int X = x; X < x + w; X++) {
749 Tile& tile = map[z][Y][X];
751 tile.flags |= flags;
752 for (int i = 0; i < 5; i++) {
753 if (exit[i]) {
754 tile.exits[i] = new Exit(*exit[i].get());
755 int dx = X - x;
756 int dy = Y - y;
757 if (wwide[i])
758 tile.exits[i]->coords.x += dx;
759 if (hwide[i])
760 tile.exits[i]->coords.y += dy;
763 for (int i = 0; i < 5; i++)
764 if (layermods[i])
765 tile.layermods[i] = layermods[i];
766 tile.enterScript = enterScript;
767 tile.leaveScript = leaveScript;
768 tile.useScript = useScript;
772 return true;
775 bool AreaTMX::splitTileFlags(const std::string& strOfFlags, unsigned* flags)
777 std::vector<std::string> strs = splitStr(strOfFlags, ",");
779 BOOST_FOREACH(const std::string& str, strs) {
780 if (str == "nowalk")
781 *flags |= TILE_NOWALK;
782 else if (str == "nowalk_player")
783 *flags |= TILE_NOWALK_PLAYER;
784 else if (str == "nowalk_npc")
785 *flags |= TILE_NOWALK_NPC;
786 else {
787 Log::err(descriptor, "invalid tile flag: " + str);
788 return false;
791 return true;
795 * Matches regex /\s*\d+\+?/
797 static bool isIntegerOrPlus(const std::string& s)
799 const int space = 0;
800 const int digit = 1;
801 const int sign = 2;
803 int state = space;
805 for (size_t i = 0; i < s.size(); i++) {
806 char c = s[i];
807 if (state == space) {
808 if (isspace(c)) continue;
809 else state++;
811 if (state == digit) {
812 if (isdigit(c)) continue;
813 else state++;
815 if (state == sign) {
816 if (c == '+') return true;
817 else return false;
820 return true;
823 bool AreaTMX::parseExit(const std::string& dest, Exit* exit,
824 bool* wwide, bool* hwide)
828 Format: destination area, x, y, z
829 E.g.: "babysfirst.area,1,3,0"
832 std::vector<std::string> strs = splitStr(dest, ",");
834 if (strs.size() != 4) {
835 Log::err(descriptor, "<exit />: invalid format");
836 return false;
839 std::string area = strs[0],
840 xstr = strs[1],
841 ystr = strs[2],
842 zstr = strs[3];
844 if (!isIntegerOrPlus(xstr) ||
845 !isIntegerOrPlus(ystr) ||
846 !isIntegerOrPlus(zstr)) {
847 Log::err(descriptor, "<exit />: invalid format");
848 return false;
851 exit->area = area;
852 exit->coords.x = atoi(xstr.c_str());
853 exit->coords.y = atoi(ystr.c_str());
854 exit->coords.z = atof(zstr.c_str());
856 *wwide = xstr.find('+') != std::string::npos;
857 *hwide = ystr.find('+') != std::string::npos;
859 return true;
862 bool AreaTMX::parseRGBA(const std::string& str,
863 Gosu::Color::Channel* r,
864 Gosu::Color::Channel* g,
865 Gosu::Color::Channel* b,
866 Gosu::Color::Channel* a)
868 std::vector<std::string> strs = splitStr(str, ",");
870 if (strs.size() != 4) {
871 Log::err(descriptor, "invalid RGBA format");
872 return false;
875 Gosu::Color::Channel* channels[] = { r, g, b, a };
877 for (int i = 0; i < 4; i++) {
878 std::string s = strs[i];
879 if (!isInteger(s)) {
880 Log::err(descriptor, "invalid RGBA format");
881 return false;
883 int v = atoi(s.c_str());
884 if (!(0 <= v && v < 256)) {
885 Log::err(descriptor,
886 "RGBA values must be between 0 and 255");
887 return false;
889 *channels[i] = (Gosu::Color::Channel)v;
892 return true;