finish move to Python-handled imports
[Tsunagari.git] / src / area-tmx.cpp
blobf4aa52d9e556a3246db40432431f361491b06f5e
1 /***************************************
2 ** Tsunagari Tile Engine **
3 ** area-tmx.cpp **
4 ** Copyright 2011-2013 PariahSoft LLC **
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/shared_ptr.hpp>
31 #include <Gosu/Graphics.hpp>
32 #include <Gosu/Image.hpp>
33 #include <Gosu/Math.hpp>
34 #include <Gosu/Timing.hpp>
36 #include "area-tmx.h"
37 #include "entity.h"
38 #include "log.h"
39 #include "python.h"
40 #include "reader.h"
41 #include "string.h"
42 #include "tile.h"
43 #include "window.h"
44 #include "world.h"
46 #ifdef _WIN32
47 #include "os-windows.h"
48 #endif
50 #define ASSERT(x) if (!(x)) { return false; }
52 /* NOTE: In the TMX map format used by Tiled, tileset tiles start counting
53 their Y-positions from 0, while layer tiles start counting from 1. I
54 can't imagine why the author did this, but we have to take it into
55 account.
58 AreaTMX::AreaTMX(Viewport* view,
59 Player* player,
60 const std::string& descriptor)
61 : Area(view, player, descriptor)
63 // Add TileType #0. Not used, but Tiled's gids start from 1.
64 gids.push_back(NULL);
67 AreaTMX::~AreaTMX()
71 bool AreaTMX::init()
73 return processDescriptor();
77 void AreaTMX::allocateMapLayer()
79 map.push_back(grid_t(dim.y, row_t(dim.x)));
80 grid_t& grid = map[dim.z];
81 for (int y = 0; y < dim.y; y++) {
82 row_t& row = grid[y];
83 for (int x = 0; x < dim.x; x++) {
84 Tile& tile = row[x];
85 new (&tile) Tile(this, x, y, dim.z);
88 dim.z++;
91 bool AreaTMX::processDescriptor()
93 XMLRef doc;
94 XMLNode root;
96 ASSERT(doc = Reader::getXMLDoc(descriptor, "dtd/area.dtd"));
97 ASSERT(root = doc->root()); // <map>
99 ASSERT(root.intAttr("width", &dim.x));
100 ASSERT(root.intAttr("height", &dim.y));
101 dim.z = 0;
103 for (XMLNode child = root.childrenNode(); child; child = child.next()) {
104 if (child.is("properties")) {
105 ASSERT(processMapProperties(child));
107 else if (child.is("tileset")) {
108 ASSERT(processTileSet(child));
110 else if (child.is("layer")) {
111 ASSERT(processLayer(child));
113 else if (child.is("objectgroup")) {
114 ASSERT(processObjectGroup(child));
118 return true;
121 bool AreaTMX::processMapProperties(XMLNode node)
125 <properties>
126 <property name="name" value="Wooded AreaTMX"/>
127 <property name="intro_music" value="arrive.ogg"/>
128 <property name="main_music" value="wind.ogg"/>
129 <property name="on_load" value="wood_setup.py"/>
130 <property name="on_focus" value="wood_focus.py"/>
131 <property name="on_tick" value="wood_tick.py"/>
132 <property name="on_turn" value="wood_turn.py"/>
133 <property name="loop" value="xy"/>
134 <property name="color_overlay" value="255,255,255,127"/>
135 </properties>
138 musicIntroSet = false;
139 musicLoopSet = false;
141 for (XMLNode child = node.childrenNode(); child; child = child.next()) {
142 std::string name = child.attr("name");
143 std::string value = child.attr("value");
144 if (name == "name")
145 this->name = value;
146 else if (name == "intro_music") {
147 musicIntro = value;
148 musicIntroSet = true;
150 else if (name == "main_music") {
151 musicLoop = value;
152 musicLoopSet = true;
154 else if (name == "on_load") {
155 std::string filename = value;
156 ScriptRef script = Script::create(filename);
157 if (!script || !script->validate())
158 return false;
159 loadScript = script;
161 else if (name == "on_focus") {
162 std::string filename = value;
163 ScriptRef script = Script::create(filename);
164 if (!script || !script->validate())
165 return false;
166 focusScript = script;
168 else if (name == "on_tick") {
169 std::string filename = value;
170 ScriptRef script = Script::create(filename);
171 if (!script || !script->validate())
172 return false;
173 tickScript = script;
175 else if (name == "on_turn") {
176 std::string filename = value;
177 ScriptRef script = Script::create(filename);
178 if (!script || !script->validate())
179 return false;
180 turnScript = script;
182 else if (name == "loop") {
183 loopX = value.find('x') != std::string::npos;
184 loopY = value.find('y') != std::string::npos;
186 else if (name == "color_overlay") {
187 Gosu::Color::Channel r, g, b, a;
188 ASSERT(parseRGBA(value, &r, &g, &b, &a));
189 colorOverlay = Gosu::Color(a, r, g, b);
193 return true;
196 bool AreaTMX::processTileSet(XMLNode node)
200 <tileset firstgid="1" name="tiles.sheet" tilewidth="64" tileheight="64">
201 <image source="tiles.sheet" width="256" height="256"/>
202 <tile id="14">
204 </tile>
205 </tileset>
208 XMLRef doc;
209 std::string source;
211 TileSet* set = NULL;
212 TiledImageRef img;
213 int tilex, tiley;
214 int firstGid;
216 // Read firstgid from original node.
217 ASSERT(node.intAttr("firstgid", &firstGid));
219 // If this node is just a reference to an external TSX file, load it
220 // and process the root tileset element of the TSX, instead.
221 source = node.attr("source");
222 if (source.size()) {
223 if (!(doc = Reader::getXMLDoc(source, "dtd/tsx.dtd"))) {
224 Log::err(descriptor, source + ": failed to load valid TSX file");
225 return false;
227 ASSERT(node = doc->root()); // <tileset>
230 ASSERT(node.intAttr("tilewidth", &tilex));
231 ASSERT(node.intAttr("tileheight", &tiley));
233 if (tileDim && tileDim != ivec2(tilex, tiley)) {
234 Log::err(descriptor,
235 "<tileset>'s width/height contradict earlier <layer>");
236 return false;
238 tileDim = ivec2(tilex, tiley);
240 for (XMLNode child = node.childrenNode(); child; child = child.next()) {
241 if (child.is("image")) {
242 int pixelw, pixelh;
243 ASSERT(child.intAttr("width", &pixelw) &&
244 child.intAttr("height", &pixelh));
245 int width = pixelw / tileDim.x;
246 int height = pixelh / tileDim.y;
248 std::string source = child.attr("source");
249 tileSets[source] = TileSet(width, height);
250 set = &tileSets[source];
252 // Load tileset image.
253 img = Reader::getTiledImage(source, tilex, tiley);
254 if (!img) {
255 Log::err(descriptor, "tileset image not found");
256 return false;
259 // Initialize "vanilla" tile type array.
260 for (size_t i = 0; i < img->size(); i++) {
261 ImageRef& tileImg = (*img.get())[i];
262 TileType* type = new TileType(tileImg);
263 set->add(type);
264 gids.push_back(type);
267 else if (child.is("tile")) {
268 // Handle an explicitly declared "non-vanilla" type.
270 if (!img) {
271 Log::err(descriptor,
272 "Tile type processed before tileset image loaded");
273 return false;
276 // "id" is 0-based index of a tile in the current
277 // tileset, if the tileset were a flat array.
278 int id;
279 ASSERT(child.intAttr("id", &id));
281 if (id < 0 || (int)img->size() <= id) {
282 Log::err(descriptor, "tile type id is invalid");
283 return false;
286 // Initialize a default TileType, we'll build on that.
287 TileType* type = new TileType((*img.get())[id]);
288 ASSERT(processTileType(child, *type, img, id));
290 // "gid" is the global area-wide id of the tile.
291 size_t gid = id + firstGid;
292 delete gids[gid]; // "vanilla" type
293 gids[gid] = type;
294 set->set(id, type);
298 return true;
301 bool AreaTMX::processTileType(XMLNode node, TileType& type,
302 TiledImageRef& img, int id)
306 <tile id="8">
307 <properties>
308 <property name="flags" value="nowalk"/>
309 <property name="onEnter" value="skid();speed(2)"/>
310 <property name="onLeave" value="undo()"/>
311 <property name="onUse" value="undo()"/>
312 </properties>
313 </tile>
314 <tile id="14">
315 <properties>
316 <property name="frames" value="1,2,3,4"/>
317 <property name="speed" value="2"/>
318 </properties>
319 </tile>
322 // The id has already been handled by processTileSet, so we don't have
323 // to worry about it.
325 // If a Tile is animated, it needs both member frames and a speed.
326 std::vector<ImageRef> framesvec;
327 int cycles = ANIM_INFINITE_CYCLES;
328 int frameLen = -1;
330 XMLNode child = node.childrenNode(); // <properties>
331 for (child = child.childrenNode(); child; child = child.next()) {
332 // Each <property>...
333 std::string name = child.attr("name");
334 std::string value = child.attr("value");
335 if (name == "flags") {
336 ASSERT(splitTileFlags(value, &type.flags));
338 else if (name == "on_enter") {
339 std::string filename = value;
340 ScriptRef script = Script::create(filename);
341 if (!script || !script->validate())
342 return false;
343 type.enterScript = script;
345 else if (name == "on_leave") {
346 std::string filename = value;
347 ScriptRef script = Script::create(filename);
348 if (!script || !script->validate())
349 return false;
350 type.leaveScript = script;
352 else if (name == "on_use") {
353 std::string filename = value;
354 ScriptRef script = Script::create(filename);
355 if (!script || !script->validate())
356 return false;
357 type.useScript = script;
359 else if (name == "frames") {
360 std::string memtemp;
361 std::vector<std::string> frames;
362 std::vector<std::string>::iterator it;
363 memtemp = value;
364 frames = splitStr(memtemp, ",");
366 // Make sure the first member is this tile.
367 if (atoi(frames[0].c_str()) != id) {
368 Log::err(descriptor, "first member of tile"
369 " id " + itostr(id) +
370 " animation must be itself.");
371 return false;
374 // Add frames to our animation.
375 // We already have one from TileType's constructor.
376 for (it = frames.begin(); it < frames.end(); it++) {
377 int idx = atoi(it->c_str());
378 if (idx < 0 || (int)img->size() <= idx) {
379 Log::err(descriptor, "frame index out "
380 "of range for animated tile");
381 return false;
383 framesvec.push_back((*img.get())[idx]);
386 else if (name == "speed") {
387 double hertz;
388 ASSERT(child.doubleAttr("value", &hertz));
389 frameLen = (int)(1000.0/hertz);
391 else if (name == "cycles") {
392 ASSERT(child.intAttr("value", &cycles));
396 if (framesvec.size() || frameLen != -1) {
397 if (framesvec.empty() || frameLen == -1) {
398 Log::err(descriptor, "tile type must either have both "
399 "frames and speed or none");
400 return false;
402 // Add 'now' to Animation constructor??
403 time_t now = World::instance()->time();
404 type.anim = Animation(framesvec, frameLen);
405 type.anim.startOver(now, cycles);
408 return true;
411 bool AreaTMX::processLayer(XMLNode node)
415 <layer name="Tiles0" width="5" height="5">
416 <properties>
418 </properties>
419 <data>
420 <tile gid="9"/>
421 <tile gid="9"/>
422 <tile gid="9"/>
424 <tile gid="3"/>
425 <tile gid="9"/>
426 <tile gid="9"/>
427 </data>
428 </layer>
431 int x, y;
432 double depth;
433 ASSERT(node.intAttr("width", &x));
434 ASSERT(node.intAttr("height", &y));
436 if (dim.x != x || dim.y != y) {
437 Log::err(descriptor, "layer x,y size != map x,y size");
438 return false;
441 allocateMapLayer();
443 for (XMLNode child = node.childrenNode(); child; child = child.next()) {
444 if (child.is("properties")) {
445 ASSERT(processLayerProperties(child, &depth));
447 else if (child.is("data")) {
448 ASSERT(processLayerData(child, dim.z - 1));
452 return true;
455 bool AreaTMX::processLayerProperties(XMLNode node, double* depth)
459 <properties>
460 <property name="layer" value="0"/>
461 </properties>
464 bool layerFound = false;
466 for (XMLNode child = node.childrenNode(); child; child = child.next()) {
467 std::string name = child.attr("name");
468 std::string value = child.attr("value");
469 if (name == "layer") {
470 layerFound = true;
471 ASSERT(child.doubleAttr("value", depth));
472 if (depth2idx.find(*depth) != depth2idx.end()) {
473 Log::err(descriptor,
474 "depth used multiple times");
475 return false;
478 depth2idx[*depth] = dim.z - 1;
479 idx2depth.push_back(*depth);
480 // Effectively idx2depth[dim.z - 1] = depth;
484 if (!layerFound)
485 Log::err(descriptor, "<layer> must have layer property");
486 return layerFound;
489 bool AreaTMX::processLayerData(XMLNode node, int z)
493 <data>
494 <tile gid="9"/>
495 <tile gid="9"/>
496 <tile gid="9"/>
498 <tile gid="3"/>
499 <tile gid="9"/>
500 <tile gid="9"/>
501 </data>
504 int x = 0, y = 0;
506 for (XMLNode child = node.childrenNode(); child; child = child.next()) {
507 if (child.is("tile")) {
508 int gid;
509 ASSERT(child.intAttr("gid", &gid));
511 if (gid < 0 || (int)gids.size() <= gid) {
512 Log::err(descriptor, "invalid tile gid");
513 return false;
516 // A gid of zero means there is no tile at this
517 // position on this layer.
518 if (gid > 0) {
519 TileType* type = gids[gid];
520 Tile& tile = map[z][y][x];
521 type->allOfType.push_back(&tile);
522 tile.parent = type;
525 if (++x == dim.x) {
526 x = 0;
527 y++;
532 return true;
535 bool AreaTMX::processObjectGroup(XMLNode node)
539 <objectgroup name="Prop0" width="5" height="5">
540 <properties>
541 <property name="layer" value="0.0"/>
542 </properties>
543 <object name="tile2" gid="7" x="64" y="320">
544 <properties>
545 <property name="onEnter" value="speed(0.5)"/>
546 <property name="onLeave" value="undo()"/>
547 <property name="onUse" value="undo()"/>
548 <property name="exit" value="grassfield.area,1,1,0"/>
549 <property name="flags" value="npc_nowalk"/>
550 </properties>
551 </object>
552 </objectgroup>
555 double invalid = (double)NAN; // Not a number.
556 int x, y;
557 ASSERT(node.intAttr("width", &x));
558 ASSERT(node.intAttr("height", &y));
560 double depth = invalid;
562 if (dim.x != x || dim.y != y) {
563 Log::err(descriptor, "objectgroup x,y size != map x,y size");
564 return false;
567 for (XMLNode child = node.childrenNode(); child; child = child.next()) {
568 if (child.is("properties")) {
569 ASSERT(processObjectGroupProperties(child, &depth));
571 else if (child.is("object")) {
572 ASSERT(depth != invalid);
573 int z = depth2idx[depth];
574 ASSERT(processObject(child, z));
578 return true;
581 bool AreaTMX::processObjectGroupProperties(XMLNode node, double* depth)
585 <properties>
586 <property name="layer" value="0.0"/>
587 </properties>
589 bool layerFound = false;
591 for (XMLNode child = node.childrenNode(); child; child = child.next()) {
592 std::string name = child.attr("name");
593 std::string value = child.attr("value");
594 if (name == "layer") {
595 layerFound = true;
596 ASSERT(child.doubleAttr("value", depth));
597 if (depth2idx.find(*depth) == depth2idx.end()) {
598 allocateMapLayer();
599 depth2idx[*depth] = dim.z - 1;
600 idx2depth.push_back(*depth);
601 // Effectively idx2depth[dim.z - 1] = depth;
606 if (!layerFound)
607 Log::err(descriptor, "<objectgroup> must have layer property");
608 return layerFound;
611 bool AreaTMX::processObject(XMLNode node, int z)
615 <object name="tile2" gid="7" x="64" y="320">
616 <properties>
617 <property name="onEnter" value="speed(0.5)"/>
618 <property name="onLeave" value="undo()"/>
619 <property name="onUse" value="undo()"/>
620 <property name="exit" value="grassfield.area,1,1,0"/>
621 <property name="flags" value="npc_nowalk"/>
622 </properties>
623 </object>
624 <object name="foo" x="0" y="0" width="64" height="64">
626 </object>
629 // Gather object properties now. Assign them to tiles later.
630 bool wwide[5], hwide[5]; /* wide exit in dimensions: width, height */
632 ScriptRef enterScript, leaveScript, useScript;
633 boost::scoped_ptr<Exit> exit[5];
634 boost::scoped_ptr<double> layermods[5];
635 unsigned flags = 0x0;
637 XMLNode child = node.childrenNode(); // <properties>
638 if (!child) {
639 // Empty <object> element. Odd, but acceptable.
640 return true;
642 for (child = child.childrenNode(); child; child = child.next()) {
643 // Each <property>...
644 std::string name = child.attr("name");
645 std::string value = child.attr("value");
646 if (name == "flags") {
647 ASSERT(splitTileFlags(value, &flags));
649 else if (name == "on_enter") {
650 std::string filename = value;
651 ScriptRef script = Script::create(filename);
652 if (!script || !script->validate())
653 return false;
654 enterScript = script;
656 else if (name == "on_leave") {
657 std::string filename = value;
658 ScriptRef script = Script::create(filename);
659 if (!script || !script->validate())
660 return false;
661 leaveScript = script;
663 else if (name == "on_use") {
664 std::string filename = value;
665 ScriptRef script = Script::create(filename);
666 if (!script || !script->validate())
667 return false;
668 useScript = script;
670 else if (name == "exit") {
671 exit[EXIT_NORMAL].reset(new Exit);
672 ASSERT(parseExit(value, exit[EXIT_NORMAL].get(), &wwide[EXIT_NORMAL], &hwide[EXIT_NORMAL]));
673 flags |= TILE_NOWALK_NPC;
675 else if (name == "exit:up") {
676 exit[EXIT_UP].reset(new Exit);
677 ASSERT(parseExit(value, exit[EXIT_UP].get(), &wwide[EXIT_UP], &hwide[EXIT_UP]));
679 else if (name == "exit:down") {
680 exit[EXIT_DOWN].reset(new Exit);
681 ASSERT(parseExit(value, exit[EXIT_DOWN].get(), &wwide[EXIT_DOWN], &hwide[EXIT_DOWN]));
683 else if (name == "exit:left") {
684 exit[EXIT_LEFT].reset(new Exit);
685 ASSERT(parseExit(value, exit[EXIT_LEFT].get(), &wwide[EXIT_LEFT], &hwide[EXIT_LEFT]));
687 else if (name == "exit:right") {
688 exit[EXIT_RIGHT].reset(new Exit);
689 ASSERT(parseExit(value, exit[EXIT_RIGHT].get(), &wwide[EXIT_RIGHT], &hwide[EXIT_RIGHT]));
691 else if (name == "layermod") {
692 double mod;
693 ASSERT(child.doubleAttr("value", &mod));
694 layermods[EXIT_NORMAL].reset(new double(mod));
695 flags |= TILE_NOWALK_NPC;
697 else if (name == "layermod:up") {
698 double mod;
699 ASSERT(child.doubleAttr("value", &mod));
700 layermods[EXIT_UP].reset(new double(mod));
702 else if (name == "layermod:down") {
703 double mod;
704 ASSERT(child.doubleAttr("value", &mod));
705 layermods[EXIT_DOWN].reset(new double(mod));
707 else if (name == "layermod:left") {
708 double mod;
709 ASSERT(child.doubleAttr("value", &mod));
710 layermods[EXIT_LEFT].reset(new double(mod));
712 else if (name == "layermod:right") {
713 double mod;
714 ASSERT(child.doubleAttr("value", &mod));
715 layermods[EXIT_RIGHT].reset(new double(mod));
719 // Apply these properties directly to one or more tiles in a rectangle
720 // of the map. We don't keep an intermediary "object" object lying
721 // around.
722 int x, y, w, h;
723 ASSERT(node.intAttr("x", &x));
724 ASSERT(node.intAttr("y", &y));
725 x /= tileDim.x;
726 y /= tileDim.y;
728 if (node.hasAttr("gid")) {
729 // This is one of Tiled's "Tile Objects". It is one tile wide
730 // and high.
732 // Bug in tiled. The y is off by one. The author of the format
733 // knows about this, but it will not change.
734 y = y - 1;
735 w = 1;
736 h = 1;
738 // We don't actually use the object gid. It is supposed to
739 // indicate which tile our object is rendered as, but for
740 // Tsunagari, tile objects are always transparent and reveal
741 // the tile below.
743 else {
744 // This is one of Tiled's "Objects". It has a width and height.
745 ASSERT(node.intAttr("width", &w));
746 ASSERT(node.intAttr("height", &h));
747 w /= tileDim.x;
748 h /= tileDim.y;
751 // We know which Tiles are being talked about now... yay
752 for (int Y = y; Y < y + h; Y++) {
753 for (int X = x; X < x + w; X++) {
754 Tile& tile = map[z][Y][X];
756 tile.flags |= flags;
757 for (int i = 0; i < 5; i++) {
758 if (exit[i]) {
759 tile.exits[i] = new Exit(*exit[i].get());
760 int dx = X - x;
761 int dy = Y - y;
762 if (wwide[i])
763 tile.exits[i]->coords.x += dx;
764 if (hwide[i])
765 tile.exits[i]->coords.y += dy;
768 for (int i = 0; i < 5; i++)
769 tile.layermods[i] = layermods[i] ? new double(*layermods[i].get()) : NULL;
770 tile.enterScript = enterScript;
771 tile.leaveScript = leaveScript;
772 tile.useScript = useScript;
776 return true;
779 bool AreaTMX::splitTileFlags(const std::string& strOfFlags, unsigned* flags)
781 typedef std::vector<std::string> StringVector;
782 StringVector strs = splitStr(strOfFlags, ",");
784 for (StringVector::const_iterator it = strs.begin(); it != strs.end(); it++) {
785 const std::string& str = *it;
786 if (str == "nowalk")
787 *flags |= TILE_NOWALK;
788 else if (str == "nowalk_player")
789 *flags |= TILE_NOWALK_PLAYER;
790 else if (str == "nowalk_npc")
791 *flags |= TILE_NOWALK_NPC;
792 else {
793 Log::err(descriptor, "invalid tile flag: " + str);
794 return false;
797 return true;
801 * Matches regex /\s*\d+\+?/
803 static bool isIntegerOrPlus(const std::string& s)
805 const int space = 0;
806 const int digit = 1;
807 const int sign = 2;
809 int state = space;
811 for (size_t i = 0; i < s.size(); i++) {
812 char c = s[i];
813 if (state == space) {
814 if (isspace(c)) continue;
815 else state++;
817 if (state == digit) {
818 if (isdigit(c)) continue;
819 else state++;
821 if (state == sign) {
822 if (c == '+') return true;
823 else return false;
826 return true;
829 bool AreaTMX::parseExit(const std::string& dest, Exit* exit,
830 bool* wwide, bool* hwide)
834 Format: destination area, x, y, z
835 E.g.: "babysfirst.area,1,3,0"
838 std::vector<std::string> strs = splitStr(dest, ",");
840 if (strs.size() != 4) {
841 Log::err(descriptor, "<exit />: invalid format");
842 return false;
845 std::string area = strs[0],
846 xstr = strs[1],
847 ystr = strs[2],
848 zstr = strs[3];
850 if (!isIntegerOrPlus(xstr) ||
851 !isIntegerOrPlus(ystr) ||
852 !isIntegerOrPlus(zstr)) {
853 Log::err(descriptor, "<exit />: invalid format");
854 return false;
857 exit->area = area;
858 exit->coords.x = atoi(xstr.c_str());
859 exit->coords.y = atoi(ystr.c_str());
860 exit->coords.z = atof(zstr.c_str());
862 *wwide = xstr.find('+') != std::string::npos;
863 *hwide = ystr.find('+') != std::string::npos;
865 return true;
868 bool AreaTMX::parseRGBA(const std::string& str,
869 Gosu::Color::Channel* r,
870 Gosu::Color::Channel* g,
871 Gosu::Color::Channel* b,
872 Gosu::Color::Channel* a)
874 std::vector<std::string> strs = splitStr(str, ",");
876 if (strs.size() != 4) {
877 Log::err(descriptor, "invalid RGBA format");
878 return false;
881 Gosu::Color::Channel* channels[] = { r, g, b, a };
883 for (int i = 0; i < 4; i++) {
884 std::string s = strs[i];
885 if (!isInteger(s)) {
886 Log::err(descriptor, "invalid RGBA format");
887 return false;
889 int v = atoi(s.c_str());
890 if (!(0 <= v && v < 256)) {
891 Log::err(descriptor,
892 "RGBA values must be between 0 and 255");
893 return false;
895 *channels[i] = (Gosu::Color::Channel)v;
898 return true;