window.cpp: ~GameWindow(): delete Resourcer
[Tsunagari.git] / src / area.cpp
blobee94535bdd74851a25c7af90baab86d7e8ee6f94
1 /******************************
2 ** Tsunagari Tile Engine **
3 ** area.cpp **
4 ** Copyright 2011 OmegaSDG **
5 ******************************/
7 #include <boost/foreach.hpp>
8 #include <boost/shared_ptr.hpp>
10 #include "area.h"
11 #include "common.h"
12 #include "entity.h"
13 #include "log.h"
14 #include "resourcer.h"
15 #include "sprite.h"
16 #include "window.h"
17 #include "world.h"
19 /* NOTE: Tileset tiles start counting their positions from 0, while layer tiles
20 start counting from 1. I can't imagine why the author did this, but we
21 have to take it into account.
24 Area::Area(Resourcer* rc,
25 World* world,
26 Entity* player,
27 const std::string& descriptor)
28 : rc(rc), world(world), player(player), descriptor(descriptor)
30 dim.x = dim.y = dim.z = 0;
33 Area::~Area()
35 // Delete each Tile. If a Tile has an allocated Door struct, delete
36 // that as well.
37 BOOST_FOREACH(grid_t grid, map) {
38 BOOST_FOREACH(row_t row, grid) {
39 BOOST_FOREACH(Tile* tile, row) {
40 delete tile->door;
41 delete tile;
46 // Each Area owns its own Tileset objects. Delete tileset graphics.
47 BOOST_FOREACH(Tileset tileset, tilesets) {
48 BOOST_FOREACH(TileType type, tileset.defaults) {
49 BOOST_FOREACH(Gosu::Image* img, type.graphics) {
50 delete img;
52 // TODO: delete TileEvents when we start using them
57 bool Area::init()
59 return processDescriptor();
62 void Area::buttonDown(const Gosu::Button btn)
64 bool attemptingMove = false;
65 coord_t posMove;
67 if (btn == Gosu::kbRight) {
68 posMove = coord(1, 0, 0);
69 attemptingMove = true;
71 else if (btn == Gosu::kbLeft) {
72 posMove = coord(-1, 0, 0);
73 attemptingMove = true;
75 else if (btn == Gosu::kbUp) {
76 posMove = coord(0, -1, 0);
77 attemptingMove = true;
79 else if (btn == Gosu::kbDown) {
80 posMove = coord(0, 1, 0);
81 attemptingMove = true;
84 if (attemptingMove) {
85 coord_t newCoord = player->getCoordsByTile();
86 newCoord.x += posMove.x;
87 newCoord.y += posMove.y;
88 newCoord.z += posMove.z;
89 Tile* dest = getTile(newCoord);
90 if ((dest->flags & player_nowalk) != 0 ||
91 (dest->type->flags & player_nowalk) != 0) {
92 // The tile we're trying to move onto is set as nowalk
93 // for the player. Stop here.
94 return;
96 if (dest->door) {
97 world->loadArea(dest->door->area, dest->door->coord);
99 else {
100 player->moveByTile(posMove);
105 void Area::draw()
107 GameWindow* window = GameWindow::getWindow();
108 Gosu::Graphics* graphics = &window->graphics();
109 Gosu::Transform trans = translateCoords();
110 graphics->pushTransform(trans);
112 for (unsigned int layer = 0; layer != map.size(); layer++)
114 grid_t grid = map[layer];
115 for (unsigned int y = 0; y != grid.size(); y++)
117 row_t row = grid[y];
118 for (unsigned int x = 0; x != row.size(); x++)
120 // TODO support animations
121 Tile* tile = row[x];
122 Gosu::Image* img = tile->type->graphics[0];
123 img->draw(x*img->width(), y*img->height(), 0);
127 player->draw();
129 graphics->popTransform();
132 //! Returns the number closest to x within the range [low, high].
134 \param low Lowest possible return.
135 \param x Value to be bounded.
136 \param high Highest possible return.
137 \return A number close to x.
138 \sa center() and translateCoords()
140 static double bound(double low, double x, double high)
142 if (low > x)
143 x = low;
144 if (x > high)
145 x = high;
146 return x;
149 static double center(double w, double g, double p)
151 return w>g ? (w-g)/2.0 : bound(w-g, w/2.0-p, 0);
154 Gosu::Transform Area::translateCoords()
156 GameWindow* window = GameWindow::getWindow();
157 Gosu::Graphics* graphics = &window->graphics();
159 double tileWidth = tilesets[0].tiledim.x;
160 double tileHeight = tilesets[0].tiledim.y;
161 double windowWidth = graphics->width() / tileWidth;
162 double windowHeight = graphics->height() / tileHeight;
163 double gridWidth = dim.x;
164 double gridHeight = dim.y;
165 double playerX = player->getCoordsByPixel().x / tileWidth + 0.5;
166 double playerY = player->getCoordsByPixel().y / tileHeight + 0.5;
168 coord_t c;
169 c.x = center(windowWidth, gridWidth, playerX) * tileWidth;
170 c.y = center(windowHeight, gridHeight, playerY) * tileHeight;
172 Gosu::Transform trans = Gosu::translate(c.x, c.y);
173 return trans;
176 bool Area::needsRedraw() const
178 return player->needsRedraw();
181 bool Area::processDescriptor()
183 xmlDoc* doc = rc->getXMLDoc(descriptor);
184 if (!doc)
185 return false;
187 // use RAII to ensure doc is freed
188 boost::shared_ptr<void> alwaysFreeTheDoc(doc, xmlFreeDoc);
190 // Iterate and process children of <map>
191 xmlNode* root = xmlDocGetRootElement(doc); // <map> element
193 xmlChar* width = xmlGetProp(root, BAD_CAST("width"));
194 xmlChar* height = xmlGetProp(root, BAD_CAST("height"));
195 dim.x = atol((const char*)width);
196 dim.y = atol((const char*)height);
198 xmlNode* child = root->xmlChildrenNode;
199 for (; child != NULL; child = child->next) {
200 if (!xmlStrncmp(child->name, BAD_CAST("properties"), 11)) {
201 if (!processMapProperties(child))
202 return false;
204 else if (!xmlStrncmp(child->name, BAD_CAST("tileset"), 8)) {
205 if (!processTileset(child))
206 return false;
208 else if (!xmlStrncmp(child->name, BAD_CAST("layer"), 6)) {
209 if (!processLayer(child))
210 return false;
212 else if (!xmlStrncmp(child->name, BAD_CAST("objectgroup"), 12)) {
213 if (!processObjectGroup(child))
214 return false;
218 return true;
221 bool Area::processMapProperties(xmlNode* node)
225 <properties>
226 <property name="areaspec" value="1"/>
227 <property name="author" value="Michael D. Reiley"/>
228 <property name="name" value="Baby's First Area"/>
229 <property name="music_loop" value="true"/>
230 <property name="music_main" value="wind.music"/>
231 <property name="onLoad" value="babysfirst_init()"/>
232 <property name="scripts" value="areainits.event,test.event"/>
233 </properties>
236 xmlNode* child = node->xmlChildrenNode;
237 for (; child != NULL; child = child->next) {
238 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
239 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
240 if (!xmlStrncmp(name, BAD_CAST("author"), 7))
241 author = (const char*)value;
242 else if (!xmlStrncmp(name, BAD_CAST("name"), 5))
243 this->name = (const char*)value;
244 else if (!xmlStrncmp(name, BAD_CAST("music_loop"), 11))
245 main.loop = parseBool((const char*)value);
246 else if (!xmlStrncmp(name, BAD_CAST("music_main"), 11))
247 main.filename = (const char*)value;
248 else if (!xmlStrncmp(name, BAD_CAST("onLoad"), 7))
249 onLoadEvents = (const char*)value;
250 else if (!xmlStrncmp(name, BAD_CAST("scripts"), 8))
251 scripts = (const char*)value; // TODO split(), load
253 return true;
256 bool Area::processTileset(xmlNode* node)
260 <tileset firstgid="1" name="tiles.sheet" tilewidth="64" tileheight="64">
261 <image source="tiles.sheet" width="256" height="256"/>
262 <tile id="14">
264 </tile>
265 </tileset>
267 Tileset ts;
268 xmlChar* width = xmlGetProp(node, BAD_CAST("tilewidth"));
269 xmlChar* height = xmlGetProp(node, BAD_CAST("tileheight"));
270 ts.tiledim.x = atol((const char*)width);
271 ts.tiledim.y = atol((const char*)height);
273 xmlNode* child = node->xmlChildrenNode;
274 for (; child != NULL; child = child->next) {
275 if (!xmlStrncmp(child->name, BAD_CAST("tile"), 5)) {
276 xmlChar* idstr = xmlGetProp(child, BAD_CAST("id"));
277 unsigned id = atol((const char*)idstr);
279 // Undeclared TileTypes have default properties.
280 while (ts.defaults.size() != id) {
281 TileType tt = defaultTileType(ts.source,
282 ts.tiledim, ts.defaults.size());
283 ts.defaults.push_back(tt);
286 // Handle explicit TileType
287 if (!processTileType(child, ts))
288 return false;
290 else if (!xmlStrncmp(child->name, BAD_CAST("image"), 6)) {
291 xmlChar* source = xmlGetProp(child, BAD_CAST("source"));
292 ts.source = rc->getBitmap((const char*)source);
296 // Generate default tile types in range (m,n] where m is the last
297 // explicitly declared type and n is the number we require.
298 unsigned srcsz = ts.source.width() * ts.source.height();
299 unsigned tilesz = ts.tiledim.x * ts.tiledim.y;
300 while (ts.defaults.size() != srcsz / tilesz) {
301 TileType tt = defaultTileType(ts.source,
302 ts.tiledim, ts.defaults.size());
303 ts.defaults.push_back(tt);
306 tilesets.push_back(ts);
307 return true;
310 Area::TileType Area::defaultTileType(const Gosu::Bitmap source, coord_t tiledim,
311 int id)
313 int x = (tiledim.x * id) % source.width();
314 int y = (tiledim.y * id) / source.height() * tiledim.y; // ???
316 TileType tt;
317 Gosu::Image* img = rc->bitmapSection(source, x, y,
318 tiledim.x, tiledim.y, true);
319 tt.graphics.push_back(img);
320 tt.animated = false;
321 tt.ani_speed = 0.0;
322 tt.flags = 0x0;
323 return tt;
326 bool Area::processTileType(xmlNode* node, Tileset& ts)
330 <tile id="8">
331 <properties>
332 <property name="flags" value="nowalk"/>
333 <property name="onEnter" value="skid();speed(2)"/>
334 <property name="onLeave" value="undo()"/>
335 </properties>
336 </tile>
337 <tile id="14">
338 <properties>
339 <property name="animated" value="1"/>
340 <property name="size" value="2"/>
341 <property name="speed" value="2"/>
342 </properties>
343 </tile>
346 // Initialize a default TileType, we'll build on that.
347 TileType tt = defaultTileType(ts.source,
348 ts.tiledim, ts.defaults.size());
350 xmlChar* idstr = xmlGetProp(node, BAD_CAST("id"));
351 unsigned id = atol((const char*)idstr);
352 if (id != ts.defaults.size()) {
353 // XXX we need to know the Area we're loading...
354 Log::err("unknown area", std::string("expected TileType id ") +
355 itostr(ts.defaults.size()) + ", but got " + itostr(id));
356 return false;
359 xmlNode* child = node->xmlChildrenNode; // <properties>
360 child = child->xmlChildrenNode; // <property>
361 for (; child != NULL; child = child->next) {
362 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
363 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
364 if (!xmlStrncmp(name, BAD_CAST("flags"), 6)) {
365 tt.flags = splitTileFlags((const char*)value);
367 else if (!xmlStrncmp(name, BAD_CAST("onEnter"), 8)) {
368 // TODO events
370 else if (!xmlStrncmp(name, BAD_CAST("onLeave"), 8)) {
371 // TODO events
373 else if (!xmlStrncmp(name, BAD_CAST("animated"), 9)) {
374 tt.animated = parseBool((const char*)value);
376 else if (!xmlStrncmp(name, BAD_CAST("size"), 5)) {
377 // TODO animation
379 else if (!xmlStrncmp(name, BAD_CAST("speed"), 6)) {
380 tt.ani_speed = atol((const char*)value);
384 ts.defaults.push_back(tt);
385 return true;
388 bool Area::processLayer(xmlNode* node)
392 <layer name="Tiles0" width="5" height="5">
393 <properties>
395 </properties>
396 <data>
397 <tile gid="9"/>
398 <tile gid="9"/>
399 <tile gid="9"/>
401 <tile gid="3"/>
402 <tile gid="9"/>
403 <tile gid="9"/>
404 </data>
405 </layer>
408 xmlChar* width = xmlGetProp(node, BAD_CAST("width"));
409 xmlChar* height = xmlGetProp(node, BAD_CAST("height"));
410 int x = atol((const char*)width);
411 int y = atol((const char*)height);
413 if (dim.x != x || dim.y != y) {
414 // XXX we need to know the Area we're loading...
415 Log::err("unknown area", "layer x,y size != map x,y size");
416 return false;
419 xmlNode* child = node->xmlChildrenNode;
420 for (; child != NULL; child = child->next) {
421 if (!xmlStrncmp(child->name, BAD_CAST("properties"), 11)) {
422 if (!processLayerProperties(child))
423 return false;
425 else if (!xmlStrncmp(child->name, BAD_CAST("data"), 5)) {
426 if (!processLayerData(child))
427 return false;
430 return true;
433 bool Area::processLayerProperties(xmlNode* node)
437 <properties>
438 <property name="layer" value="0"/>
439 </properties>
442 xmlNode* child = node->xmlChildrenNode;
443 for (; child != NULL; child = child->next) {
444 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
445 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
446 if (!xmlStrncmp(name, BAD_CAST("layer"), 6)) {
447 int depth = atol((const char*)value);
448 if (depth != dim.z) {
449 Log::err("unknown area", "invalid layer depth");
450 return false;
455 return true;
458 bool Area::processLayerData(xmlNode* node)
462 <data>
463 <tile gid="9"/>
464 <tile gid="9"/>
465 <tile gid="9"/>
467 <tile gid="3"/>
468 <tile gid="9"/>
469 <tile gid="9"/>
470 </data>
473 row_t row;
474 grid_t grid;
476 xmlNode* child = node->xmlChildrenNode;
477 for (int i = 1; child != NULL; i++, child = child->next) {
478 if (!xmlStrncmp(child->name, BAD_CAST("tile"), 5)) {
479 xmlChar* gidStr = xmlGetProp(child, BAD_CAST("gid"));
480 unsigned gid = atol((const char*)gidStr)-1;
481 Tile* t = new Tile;
482 t->type = &tilesets[0].defaults[gid]; // XXX can only access first tileset
483 t->flags = 0x0;
484 t->door = NULL;
485 row.push_back(t);
486 if (row.size() % dim.x == 0) {
487 grid.push_back(row);
488 row.clear();
493 map.push_back(grid);
494 dim.z++;
495 return true;
498 bool Area::processObjectGroup(xmlNode* node)
502 <objectgroup name="Prop0" width="5" height="5">
503 <properties>
504 <property name="layer" value="0"/>
505 </properties>
506 <object name="tile2" type="Tile" gid="7" x="64" y="320">
507 <properties>
508 <property name="onEnter" value="speed(0.5)"/>
509 <property name="onLeave" value="undo()"/>
510 <property name="door" value="grassfield.area,1,1,0"/>
511 <property name="flags" value="npc_nowalk"/>
512 </properties>
513 </object>
514 </objectgroup>
517 xmlChar* width = xmlGetProp(node, BAD_CAST("width"));
518 xmlChar* height = xmlGetProp(node, BAD_CAST("height"));
519 int x = atol((const char*)width);
520 int y = atol((const char*)height);
522 int zpos = -1;
524 if (dim.x != x || dim.y != y) {
525 // XXX we need to know the Area we're loading...
526 Log::err("unknown area", "objectgroup x,y size != map x,y size");
527 return false;
530 xmlNode* child = node->xmlChildrenNode;
531 for (; child != NULL; child = child->next) {
532 if (!xmlStrncmp(child->name, BAD_CAST("properties"), 11)) {
533 if (!processObjectGroupProperties(child, &zpos))
534 return false;
536 else if (!xmlStrncmp(child->name, BAD_CAST("object"), 7)) {
537 if (zpos == -1 || !processObject(child, zpos))
538 return false;
542 return true;
545 bool Area::processObjectGroupProperties(xmlNode* node, int* zpos)
549 <properties>
550 <property name="layer" value="0"/>
551 </properties>
554 xmlNode* child = node->xmlChildrenNode;
555 for (; child != NULL; child = child->next) {
556 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
557 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
558 if (!xmlStrncmp(name, BAD_CAST("layer"), 6)) {
559 int layer = atol((const char*)value);
560 if (0 < layer || layer >= (int)dim.z) {
561 // XXX we need to know the Area we're loading...
562 Log::err("unknown area",
563 "objectgroup must correspond with layer"
565 return false;
567 *zpos = layer;
570 return true;
573 bool Area::processObject(xmlNode* node, int zpos)
577 <object name="tile2" type="Tile" gid="7" x="64" y="320">
578 <properties>
579 <property name="onEnter" value="speed(0.5)"/>
580 <property name="onLeave" value="undo()"/>
581 <property name="door" value="grassfield.area,1,1,0"/>
582 <property name="flags" value="npc_nowalk"/>
583 </properties>
584 </object>
587 xmlChar* type = xmlGetProp(node, BAD_CAST("type"));
588 if (xmlStrncmp(type, BAD_CAST("Tile"), 5)) {
589 Log::err("unknown area", "object type must be Tile");
590 return false;
593 xmlChar* xStr = xmlGetProp(node, BAD_CAST("x"));
594 xmlChar* yStr = xmlGetProp(node, BAD_CAST("y"));
595 // XXX we ignore the object gid... is that okay?
597 // wouldn't have to access tilesets if we had tiledim ourselves
598 int x = atol((const char*)xStr) / tilesets[0].tiledim.x;
599 int y = atol((const char*)yStr) / tilesets[0].tiledim.y;
600 y = y - 1; // bug in tiled? y is 1 too high
602 // We know which Tile is being talked about now... yay
603 Tile* t = map[zpos][y][x];
605 xmlNode* child = node->xmlChildrenNode; // <properties>
606 child = child->xmlChildrenNode; // <property>
607 for (; child != NULL; child = child->next) {
608 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
609 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
610 if (!xmlStrncmp(name, BAD_CAST("flags"), 6)) {
611 t->flags = splitTileFlags((const char*)value);
613 else if (!xmlStrncmp(name, BAD_CAST("onEnter"), 8)) {
614 // TODO events
616 else if (!xmlStrncmp(name, BAD_CAST("onLeave"), 8)) {
617 // TODO events
619 else if (!xmlStrncmp(name, BAD_CAST("door"), 5)) {
620 t->door = parseDoor((const char*)value);
623 return true;
626 unsigned Area::splitTileFlags(const std::string strOfFlags)
628 std::vector<std::string> strs;
629 strs = splitStr(strOfFlags, ",");
631 unsigned flags = 0x0;
632 BOOST_FOREACH(std::string str, strs)
634 // TODO: reimplement comparisons as a hash table
635 if (str == "nowalk") {
636 flags |= nowalk;
639 return flags;
642 Area::Door* Area::parseDoor(const std::string dest)
644 std::vector<std::string> strs;
645 strs = splitStr(dest, ",");
647 // TODO: verify the validity of the input string... it's coming from
648 // user land
649 Door* door = new Door;
650 door->area = strs[0];
651 door->coord.x = atol(strs[1].c_str());
652 door->coord.y = atol(strs[2].c_str());
653 door->coord.z = atol(strs[3].c_str());
654 return door;
657 coord_t Area::getDimensions() const
659 return dim;
662 Area::Tile* Area::getTile(coord_t c)
664 return map[c.z][c.y][c.x];