area: fix compiler warnings
[Tsunagari.git] / src / area.cpp
bloba6bf4c57294cb001732194094589fde32be11739
1 /******************************
2 ** Tsunagari Tile Engine **
3 ** area.cpp **
4 ** Copyright 2011 OmegaSDG **
5 ******************************/
7 #include <math.h>
9 #include <boost/foreach.hpp>
10 #include <boost/shared_ptr.hpp>
11 #include <Gosu/Graphics.hpp>
12 #include <Gosu/Image.hpp>
13 #include <Gosu/Math.hpp>
14 #include <Gosu/Timing.hpp>
16 #include "area.h"
17 #include "common.h"
18 #include "entity.h"
19 #include "log.h"
20 #include "resourcer.h"
21 #include "window.h"
22 #include "world.h"
24 /* NOTE: TileSet tiles start counting their positions from 0, while layer tiles
25 start counting from 1. I can't imagine why the author did this, but we
26 have to take it into account.
29 Area::Area(Resourcer* rc,
30 World* world,
31 Player* player,
32 const std::string& descriptor)
33 : rc(rc), world(world), player(player), descriptor(descriptor),
34 onIntro(false)
36 dim.x = dim.y = dim.z = 0;
39 Area::~Area()
41 if (musicInst && musicInst->playing())
42 musicInst->stop();
45 bool Area::init()
47 if (!processDescriptor())
48 return false;
50 if (introMusic) {
51 musicInst.reset(introMusic->play(1, 1, false));
52 onIntro = true;
54 else if (mainMusic) {
55 musicInst.reset(mainMusic->play(1, 1, true));
57 return true;
60 void Area::buttonDown(const Gosu::Button btn)
62 bool attemptingMove = false;
63 coord_t posMove;
65 if (btn == Gosu::kbRight) {
66 posMove = coord(1, 0, 0);
67 attemptingMove = true;
69 else if (btn == Gosu::kbLeft) {
70 posMove = coord(-1, 0, 0);
71 attemptingMove = true;
73 else if (btn == Gosu::kbUp) {
74 posMove = coord(0, -1, 0);
75 attemptingMove = true;
77 else if (btn == Gosu::kbDown) {
78 posMove = coord(0, 1, 0);
79 attemptingMove = true;
82 if (attemptingMove)
83 player->moveByTile(posMove);
86 void Area::draw()
88 GameWindow* window = GameWindow::getWindow();
89 Gosu::Graphics& graphics = window->graphics();
90 const Gosu::Transform trans = viewportTransform();
91 graphics.pushTransform(trans);
93 // Calculate frame to show for each type of tile
94 int millis = (int)Gosu::milliseconds();
95 BOOST_FOREACH(TileSet& set, tilesets) {
96 BOOST_FOREACH(TileType& type, set.tileTypes) {
97 if (type.animated) {
98 int frame = (millis % type.animLen) /
99 type.frameLen;
100 type.frameShowing = frame;
101 type.graphic = type.graphics[frame].get();
106 for (unsigned z = 0; z != map.size(); z++) {
107 const grid_t& grid = map[z];
108 for (unsigned y = 0; y != grid.size(); y++) {
109 const row_t& row = grid[y];
110 for (unsigned x = 0; x != row.size(); x++) {
111 const Tile& tile = row[x];
112 const TileType* type = tile.type;
113 const Gosu::Image* img = type->graphic;
114 img->draw(x*img->width(), y*img->height(), 0);
118 player->draw();
120 graphics.popTransform();
123 bool Area::needsRedraw() const
125 if (player->needsRedraw())
126 return true;
128 // Do any onscreen tile types need to update their animations?
129 int millis = (int)Gosu::milliseconds();
130 BOOST_FOREACH(const TileSet& set, tilesets) {
131 BOOST_FOREACH(const TileType& type, set.tileTypes) {
132 if (type.animated) {
133 int frame = (millis % type.animLen) /
134 type.frameLen;
135 if (frame != type.frameShowing &&
136 tileTypeOnScreen(type))
137 return true;
142 return false;
145 void Area::update(unsigned long dt)
147 if (onIntro && !musicInst->playing()) {
148 onIntro = false;
149 musicInst.reset(mainMusic->play(1, 1, true));
151 player->update(dt);
154 coord_t Area::getDimensions() const
156 return dim;
159 coord_t Area::getTileDimensions() const
161 return tilesets[0].tileDim; // XXX only considers first tileset
164 Area::Tile& Area::getTile(coord_t c)
166 return map[c.z][c.y][c.x];
169 static double center(double w, double g, double p)
171 return w>g ? (w-g)/2.0 : Gosu::boundBy(w/2.0-p, w-g, 0.0);
174 coord_t Area::viewportOffset() const
176 GameWindow* window = GameWindow::getWindow();
177 Gosu::Graphics* graphics = &window->graphics();
179 double tileWidth = (double)tilesets[0].tileDim.x;
180 double tileHeight = (double)tilesets[0].tileDim.y;
181 double windowWidth = (double)graphics->width() / tileWidth;
182 double windowHeight = (double)graphics->height() / tileHeight;
183 double gridWidth = (double)dim.x;
184 double gridHeight = (double)dim.y;
185 double playerX = (double)player->getCoordsByPixel().x /
186 tileWidth + 0.5;
187 double playerY = (double)player->getCoordsByPixel().y /
188 tileHeight + 0.5;
190 coord_t c;
191 c.x = (long)(center(windowWidth, gridWidth, playerX) * tileWidth);
192 c.y = (long)(center(windowHeight, gridHeight, playerY) * tileHeight);
193 c.z = 0;
195 return c;
198 Gosu::Transform Area::viewportTransform() const
200 coord_t c = viewportOffset();
201 Gosu::Transform trans = Gosu::translate((double)c.x, (double)c.y);
202 return trans;
205 coordcube_t Area::visibleTiles() const
207 GameWindow* window = GameWindow::getWindow();
208 Gosu::Graphics* graphics = &window->graphics();
210 long tileWidth = tilesets[0].tileDim.x;
211 long tileHeight = tilesets[0].tileDim.y;
212 int windowWidth = graphics->width();
213 int windowHeight = graphics->height();
214 coord_t off = viewportOffset();
216 coordcube_t tiles;
217 tiles.x1 = -off.x / tileWidth;
218 tiles.y1 = -off.y / tileHeight;
219 tiles.z1 = 0;
221 tiles.x2 = (long)ceil((double)(windowWidth - off.x) /
222 (double)tileWidth);
223 tiles.y2 = (long)ceil((double)(windowHeight - off.y) /
224 (double)tileHeight);
225 tiles.z2 = 1;
226 return tiles;
229 bool Area::tileTypeOnScreen(const Area::TileType& search) const
231 coordcube_t tiles = visibleTiles();
232 for (long z = tiles.z1; z != tiles.z2; z++) {
233 for (long y = tiles.y1; y != tiles.y2; y++) {
234 for (long x = tiles.x1; x != tiles.x2; x++) {
235 const Tile& tile = map[z][y][x];
236 const TileType* type = tile.type;
237 if (type == &search)
238 return true;
242 return false;
245 bool Area::processDescriptor()
247 XMLDocRef doc = rc->getXMLDoc(descriptor, "dtd/area.dtd");
248 if (!doc)
249 return false;
251 // Iterate and process children of <map>
252 xmlNode* root = xmlDocGetRootElement(doc.get()); // <map> element
254 xmlChar* width = xmlGetProp(root, BAD_CAST("width"));
255 xmlChar* height = xmlGetProp(root, BAD_CAST("height"));
256 dim.x = atol((const char*)width);
257 dim.y = atol((const char*)height);
259 xmlNode* child = root->xmlChildrenNode;
260 for (; child != NULL; child = child->next) {
261 if (!xmlStrncmp(child->name, BAD_CAST("properties"), 11)) {
262 if (!processMapProperties(child))
263 return false;
265 else if (!xmlStrncmp(child->name, BAD_CAST("tileset"), 8)) {
266 if (!processTileSet(child))
267 return false;
269 else if (!xmlStrncmp(child->name, BAD_CAST("layer"), 6)) {
270 if (!processLayer(child))
271 return false;
273 else if (!xmlStrncmp(child->name, BAD_CAST("objectgroup"), 12)) {
274 if (!processObjectGroup(child))
275 return false;
279 return true;
282 bool Area::processMapProperties(xmlNode* node)
286 <properties>
287 <property name="areaspec" value="1"/>
288 <property name="author" value="Michael D. Reiley"/>
289 <property name="name" value="Baby's First Area"/>
290 <property name="intro_music" value="intro.music"/>
291 <property name="main_music" value="wind.music"/>
292 <property name="onLoad" value="babysfirst_init()"/>
293 <property name="scripts" value="areainits.event,test.event"/>
294 </properties>
297 xmlNode* child = node->xmlChildrenNode;
298 for (; child != NULL; child = child->next) {
299 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
300 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
301 if (!xmlStrncmp(name, BAD_CAST("author"), 7))
302 author = (const char*)value;
303 else if (!xmlStrncmp(name, BAD_CAST("name"), 5))
304 this->name = (const char*)value;
305 else if (!xmlStrncmp(name, BAD_CAST("intro_music"), 12))
306 introMusic = rc->getSample((const char*)value);
307 else if (!xmlStrncmp(name, BAD_CAST("main_music"), 11))
308 mainMusic = rc->getSample((const char*)value);
309 else if (!xmlStrncmp(name, BAD_CAST("onLoad"), 7))
310 onLoadEvents = (const char*)value;
311 else if (!xmlStrncmp(name, BAD_CAST("scripts"), 8))
312 scripts = (const char*)value; // TODO split(), load
314 return true;
317 bool Area::processTileSet(xmlNode* node)
321 <tileset firstgid="1" name="tiles.sheet" tilewidth="64" tileheight="64">
322 <image source="tiles.sheet" width="256" height="256"/>
323 <tile id="14">
325 </tile>
326 </tileset>
328 TileSet ts;
329 xmlChar* width = xmlGetProp(node, BAD_CAST("tilewidth"));
330 xmlChar* height = xmlGetProp(node, BAD_CAST("tileheight"));
331 long x = ts.tileDim.x = atol((const char*)width);
332 long y = ts.tileDim.y = atol((const char*)height);
334 xmlNode* child = node->xmlChildrenNode;
335 for (; child != NULL; child = child->next) {
336 if (!xmlStrncmp(child->name, BAD_CAST("tile"), 5)) {
337 xmlChar* idstr = xmlGetProp(child, BAD_CAST("id"));
338 unsigned id = (unsigned)atoi((const char*)idstr);
340 // Undeclared TileTypes have default properties.
341 while (ts.tileTypes.size() != id) {
342 TileType tt = defaultTileType(ts);
343 ts.tileTypes.push_back(tt);
346 // Handle explicit TileType
347 if (!processTileType(child, ts))
348 return false;
350 else if (!xmlStrncmp(child->name, BAD_CAST("image"), 6)) {
351 const char* source = (const char*)xmlGetProp(child,
352 BAD_CAST("source"));
353 rc->getTiledImage(ts.tiles, source,
354 (unsigned)x, (unsigned)y, true);
358 while (ts.tiles.size()) {
359 TileType tt = defaultTileType(ts);
360 ts.tileTypes.push_back(tt);
363 tilesets.push_back(ts);
364 return true;
367 Area::TileType Area::defaultTileType(TileSet& set)
369 TileType type;
370 type.animated = false;
371 type.frameLen = 1000;
372 type.flags = 0x0;
373 type.graphics.push_back(set.tiles.front());
374 type.graphic = type.graphics[0].get();
375 set.tiles.pop_front();
376 return type;
379 bool Area::processTileType(xmlNode* node, TileSet& ts)
383 <tile id="8">
384 <properties>
385 <property name="flags" value="nowalk"/>
386 <property name="onEnter" value="skid();speed(2)"/>
387 <property name="onLeave" value="undo()"/>
388 </properties>
389 </tile>
390 <tile id="14">
391 <properties>
392 <property name="animated" value="1"/>
393 <property name="size" value="2"/>
394 <property name="speed" value="2"/>
395 </properties>
396 </tile>
399 // Initialize a default TileType, we'll build on that.
400 TileType tt = defaultTileType(ts);
402 xmlChar* idstr = xmlGetProp(node, BAD_CAST("id"));
403 unsigned id = (unsigned)atoi((const char*)idstr); // atoi
404 long expectedId = ts.tileTypes.size();
405 if (id != expectedId) {
406 Log::err(descriptor, std::string("expected TileType id ") +
407 itostr(expectedId) + ", but got " +
408 itostr(id));
409 return false;
412 xmlNode* child = node->xmlChildrenNode; // <properties>
413 child = child->xmlChildrenNode; // <property>
414 for (; child != NULL; child = child->next) {
415 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
416 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
417 if (!xmlStrncmp(name, BAD_CAST("flags"), 6)) {
418 tt.flags = splitTileFlags((const char*)value);
420 else if (!xmlStrncmp(name, BAD_CAST("onEnter"), 8)) {
421 // TODO events
423 else if (!xmlStrncmp(name, BAD_CAST("onLeave"), 8)) {
424 // TODO events
426 else if (!xmlStrncmp(name, BAD_CAST("animated"), 9)) {
427 tt.animated = parseBool((const char*)value);
429 else if (!xmlStrncmp(name, BAD_CAST("size"), 5)) {
430 int size = atoi((const char*)value); // atoi
432 // Add size-1 more frames to our animation.
433 // We already have one from defaultTileType.
434 for (int i = 1; i < size; i++) {
435 if (ts.tiles.empty()) {
436 Log::err(descriptor, "ran out of tiles"
437 "/frames for animated tile");
438 return false;
440 tt.graphics.push_back(ts.tiles.front());
441 ts.tiles.pop_front();
443 tt.animLen = tt.frameLen * (int)tt.graphics.size();
445 else if (!xmlStrncmp(name, BAD_CAST("speed"), 6)) {
446 tt.frameLen = (int)(1000.0/atof((const char*)value));
447 tt.animLen = tt.frameLen * (int)tt.graphics.size();
451 ts.tileTypes.push_back(tt);
452 return true;
455 bool Area::processLayer(xmlNode* node)
459 <layer name="Tiles0" width="5" height="5">
460 <properties>
462 </properties>
463 <data>
464 <tile gid="9"/>
465 <tile gid="9"/>
466 <tile gid="9"/>
468 <tile gid="3"/>
469 <tile gid="9"/>
470 <tile gid="9"/>
471 </data>
472 </layer>
475 xmlChar* width = xmlGetProp(node, BAD_CAST("width"));
476 xmlChar* height = xmlGetProp(node, BAD_CAST("height"));
477 int x = atoi((const char*)width);
478 int y = atoi((const char*)height);
480 if (dim.x != x || dim.y != y) {
481 // XXX we need to know the Area we're loading...
482 Log::err(descriptor, "layer x,y size != map x,y size");
483 return false;
486 xmlNode* child = node->xmlChildrenNode;
487 for (; child != NULL; child = child->next) {
488 if (!xmlStrncmp(child->name, BAD_CAST("properties"), 11)) {
489 if (!processLayerProperties(child))
490 return false;
492 else if (!xmlStrncmp(child->name, BAD_CAST("data"), 5)) {
493 if (!processLayerData(child))
494 return false;
497 return true;
500 bool Area::processLayerProperties(xmlNode* node)
504 <properties>
505 <property name="layer" value="0"/>
506 </properties>
509 xmlNode* child = node->xmlChildrenNode;
510 for (; child != NULL; child = child->next) {
511 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
512 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
513 if (!xmlStrncmp(name, BAD_CAST("layer"), 6)) {
514 int depth = atoi((const char*)value);
515 if (depth != dim.z) {
516 Log::err(descriptor, "invalid layer depth");
517 return false;
522 return true;
525 bool Area::processLayerData(xmlNode* node)
529 <data>
530 <tile gid="9"/>
531 <tile gid="9"/>
532 <tile gid="9"/>
534 <tile gid="3"/>
535 <tile gid="9"/>
536 <tile gid="9"/>
537 </data>
540 row_t row;
541 grid_t grid;
543 row.reserve(dim.x);
544 grid.reserve(dim.y);
546 xmlNode* child = node->xmlChildrenNode;
547 for (int i = 1; child != NULL; i++, child = child->next) {
548 if (!xmlStrncmp(child->name, BAD_CAST("tile"), 5)) {
549 xmlChar* gidStr = xmlGetProp(child, BAD_CAST("gid"));
550 unsigned gid = (unsigned)atoi((const char*)gidStr)-1;
552 // XXX can only access first tileset
553 TileType* type = &tilesets[0].tileTypes[gid];
555 Tile t;
556 t.type = type;
557 t.flags = 0x0;
558 type->allOfType.push_back(&t);
559 row.push_back(t);
560 if (row.size() % dim.x == 0) {
561 grid.push_back(row);
562 row.clear();
563 row.reserve(dim.x);
568 map.push_back(grid);
569 dim.z++;
570 return true;
573 bool Area::processObjectGroup(xmlNode* node)
577 <objectgroup name="Prop0" width="5" height="5">
578 <properties>
579 <property name="layer" value="0"/>
580 </properties>
581 <object name="tile2" type="Tile" gid="7" x="64" y="320">
582 <properties>
583 <property name="onEnter" value="speed(0.5)"/>
584 <property name="onLeave" value="undo()"/>
585 <property name="door" value="grassfield.area,1,1,0"/>
586 <property name="flags" value="npc_nowalk"/>
587 </properties>
588 </object>
589 </objectgroup>
592 xmlChar* width = xmlGetProp(node, BAD_CAST("width"));
593 xmlChar* height = xmlGetProp(node, BAD_CAST("height"));
594 int x = atoi((const char*)width);
595 int y = atoi((const char*)height);
597 int zpos = -1;
599 if (dim.x != x || dim.y != y) {
600 // XXX we need to know the Area we're loading...
601 Log::err(descriptor,
602 "objectgroup x,y size != map x,y size");
603 return false;
606 xmlNode* child = node->xmlChildrenNode;
607 for (; child != NULL; child = child->next) {
608 if (!xmlStrncmp(child->name, BAD_CAST("properties"), 11)) {
609 if (!processObjectGroupProperties(child, &zpos))
610 return false;
612 else if (!xmlStrncmp(child->name, BAD_CAST("object"), 7)) {
613 if (zpos == -1 || !processObject(child, zpos))
614 return false;
618 return true;
621 bool Area::processObjectGroupProperties(xmlNode* node, int* zpos)
625 <properties>
626 <property name="layer" value="0"/>
627 </properties>
630 xmlNode* child = node->xmlChildrenNode;
631 for (; child != NULL; child = child->next) {
632 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
633 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
634 if (!xmlStrncmp(name, BAD_CAST("layer"), 6)) {
635 int layer = atoi((const char*)value);
636 if (0 < layer || layer >= (int)dim.z) {
637 // XXX we need to know the Area we're loading...
638 Log::err(descriptor,
639 "objectgroup must correspond with layer"
641 return false;
643 *zpos = layer;
646 return true;
649 bool Area::processObject(xmlNode* node, int zpos)
653 <object name="tile2" type="Tile" gid="7" x="64" y="320">
654 <properties>
655 <property name="onEnter" value="speed(0.5)"/>
656 <property name="onLeave" value="undo()"/>
657 <property name="door" value="grassfield.area,1,1,0"/>
658 <property name="flags" value="npc_nowalk"/>
659 </properties>
660 </object>
663 xmlChar* type = xmlGetProp(node, BAD_CAST("type"));
664 if (xmlStrncmp(type, BAD_CAST("Tile"), 5)) {
665 Log::err(descriptor, "object type must be Tile");
666 return false;
669 xmlChar* xStr = xmlGetProp(node, BAD_CAST("x"));
670 xmlChar* yStr = xmlGetProp(node, BAD_CAST("y"));
671 // XXX we ignore the object gid... is that okay?
673 // wouldn't have to access tilesets if we had tileDim ourselves
674 long x = atol((const char*)xStr) / tilesets[0].tileDim.x;
675 long y = atol((const char*)yStr) / tilesets[0].tileDim.y;
676 y = y - 1; // bug in tiled? y is 1 too high
678 // We know which Tile is being talked about now... yay
679 Tile& t = map[zpos][y][x];
681 xmlNode* child = node->xmlChildrenNode; // <properties>
682 child = child->xmlChildrenNode; // <property>
683 for (; child != NULL; child = child->next) {
684 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
685 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
686 if (!xmlStrncmp(name, BAD_CAST("flags"), 6)) {
687 t.flags = splitTileFlags((const char*)value);
689 else if (!xmlStrncmp(name, BAD_CAST("onEnter"), 8)) {
690 // TODO events
692 else if (!xmlStrncmp(name, BAD_CAST("onLeave"), 8)) {
693 // TODO events
695 else if (!xmlStrncmp(name, BAD_CAST("door"), 5)) {
696 t.door.reset(parseDoor((const char*)value));
697 t.flags |= npc_nowalk;
700 return true;
703 unsigned Area::splitTileFlags(const std::string strOfFlags)
705 std::vector<std::string> strs;
706 strs = splitStr(strOfFlags, ",");
708 unsigned flags = 0x0;
709 BOOST_FOREACH(const std::string& str, strs)
711 // TODO: reimplement comparisons as a hash table
712 if (str == "nowalk") {
713 flags |= nowalk;
716 return flags;
719 Area::Door Area::parseDoor(const std::string dest)
723 Format: destination Area, x, y, z
724 E.g.: "babysfirst.area,1,3,0"
727 std::vector<std::string> strs;
728 strs = splitStr(dest, ",");
730 // TODO: verify the validity of the input string... it's coming from
731 // user land
732 Door door;
733 door.area = strs[0];
734 door.coord.x = atol(strs[1].c_str());
735 door.coord.y = atol(strs[2].c_str());
736 door.coord.z = atol(strs[3].c_str());
737 return door;