Doors are activated only *after* player moves onto them
[Tsunagari.git] / src / area.cpp
blobe2b472bd27137025dce57cf534560ce8248f349e
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 player->moveByTile(posMove);
100 void Area::draw()
102 GameWindow* window = GameWindow::getWindow();
103 Gosu::Graphics* graphics = &window->graphics();
104 Gosu::Transform trans = translateCoords();
105 graphics->pushTransform(trans);
107 for (unsigned layer = 0; layer != map.size(); layer++)
109 grid_t grid = map[layer];
110 for (unsigned y = 0; y != grid.size(); y++)
112 row_t row = grid[y];
113 for (unsigned x = 0; x != row.size(); x++)
115 // TODO support animations
116 Tile* tile = row[x];
117 Gosu::Image* img = tile->type->graphics[0];
118 img->draw(x*img->width(), y*img->height(), 0);
122 player->draw();
124 graphics->popTransform();
127 //! Returns the number closest to x within the range [low, high].
129 \param low Lowest possible return.
130 \param x Value to be bounded.
131 \param high Highest possible return.
132 \return A number close to x.
133 \sa center() and translateCoords()
135 static double bound(double low, double x, double high)
137 if (low > x)
138 x = low;
139 if (x > high)
140 x = high;
141 return x;
144 static double center(double w, double g, double p)
146 return w>g ? (w-g)/2.0 : bound(w-g, w/2.0-p, 0);
149 Gosu::Transform Area::translateCoords()
151 GameWindow* window = GameWindow::getWindow();
152 Gosu::Graphics* graphics = &window->graphics();
154 double tileWidth = (double)tilesets[0].tiledim.x;
155 double tileHeight = (double)tilesets[0].tiledim.y;
156 double windowWidth = (double)graphics->width() / tileWidth;
157 double windowHeight = (double)graphics->height() / tileHeight;
158 double gridWidth = (double)dim.x;
159 double gridHeight = (double)dim.y;
160 double playerX = (double)player->getCoordsByPixel().x / tileWidth + 0.5;
161 double playerY = (double)player->getCoordsByPixel().y / tileHeight + 0.5;
163 coord_t c;
164 c.x = (long)(center(windowWidth, gridWidth, playerX) * tileWidth);
165 c.y = (long)(center(windowHeight, gridHeight, playerY) * tileHeight);
167 Gosu::Transform trans = Gosu::translate((double)c.x, (double)c.y);
168 return trans;
171 bool Area::needsRedraw() const
173 return player->needsRedraw();
176 bool Area::processDescriptor()
178 xmlDoc* doc = rc->getXMLDoc(descriptor);
179 if (!doc)
180 return false;
182 // use RAII to ensure doc is freed
183 boost::shared_ptr<void> alwaysFreeTheDoc(doc, xmlFreeDoc);
185 // Iterate and process children of <map>
186 xmlNode* root = xmlDocGetRootElement(doc); // <map> element
188 xmlChar* width = xmlGetProp(root, BAD_CAST("width"));
189 xmlChar* height = xmlGetProp(root, BAD_CAST("height"));
190 dim.x = atol((const char*)width);
191 dim.y = atol((const char*)height);
193 xmlNode* child = root->xmlChildrenNode;
194 for (; child != NULL; child = child->next) {
195 if (!xmlStrncmp(child->name, BAD_CAST("properties"), 11)) {
196 if (!processMapProperties(child))
197 return false;
199 else if (!xmlStrncmp(child->name, BAD_CAST("tileset"), 8)) {
200 if (!processTileset(child))
201 return false;
203 else if (!xmlStrncmp(child->name, BAD_CAST("layer"), 6)) {
204 if (!processLayer(child))
205 return false;
207 else if (!xmlStrncmp(child->name, BAD_CAST("objectgroup"), 12)) {
208 if (!processObjectGroup(child))
209 return false;
213 return true;
216 bool Area::processMapProperties(xmlNode* node)
220 <properties>
221 <property name="areaspec" value="1"/>
222 <property name="author" value="Michael D. Reiley"/>
223 <property name="name" value="Baby's First Area"/>
224 <property name="music_loop" value="true"/>
225 <property name="music_main" value="wind.music"/>
226 <property name="onLoad" value="babysfirst_init()"/>
227 <property name="scripts" value="areainits.event,test.event"/>
228 </properties>
231 xmlNode* child = node->xmlChildrenNode;
232 for (; child != NULL; child = child->next) {
233 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
234 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
235 if (!xmlStrncmp(name, BAD_CAST("author"), 7))
236 author = (const char*)value;
237 else if (!xmlStrncmp(name, BAD_CAST("name"), 5))
238 this->name = (const char*)value;
239 else if (!xmlStrncmp(name, BAD_CAST("music_loop"), 11))
240 main.loop = parseBool((const char*)value);
241 else if (!xmlStrncmp(name, BAD_CAST("music_main"), 11))
242 main.filename = (const char*)value;
243 else if (!xmlStrncmp(name, BAD_CAST("onLoad"), 7))
244 onLoadEvents = (const char*)value;
245 else if (!xmlStrncmp(name, BAD_CAST("scripts"), 8))
246 scripts = (const char*)value; // TODO split(), load
248 return true;
251 bool Area::processTileset(xmlNode* node)
255 <tileset firstgid="1" name="tiles.sheet" tilewidth="64" tileheight="64">
256 <image source="tiles.sheet" width="256" height="256"/>
257 <tile id="14">
259 </tile>
260 </tileset>
262 Tileset ts;
263 xmlChar* width = xmlGetProp(node, BAD_CAST("tilewidth"));
264 xmlChar* height = xmlGetProp(node, BAD_CAST("tileheight"));
265 ts.tiledim.x = atol((const char*)width);
266 ts.tiledim.y = atol((const char*)height);
268 xmlNode* child = node->xmlChildrenNode;
269 for (; child != NULL; child = child->next) {
270 if (!xmlStrncmp(child->name, BAD_CAST("tile"), 5)) {
271 xmlChar* idstr = xmlGetProp(child, BAD_CAST("id"));
272 unsigned id = (unsigned)atoi((const char*)idstr);
274 // Undeclared TileTypes have default properties.
275 while (ts.defaults.size() != id) {
276 TileType tt = defaultTileType(ts.source,
277 ts.tiledim, (int)ts.defaults.size());
278 ts.defaults.push_back(tt);
281 // Handle explicit TileType
282 if (!processTileType(child, ts))
283 return false;
285 else if (!xmlStrncmp(child->name, BAD_CAST("image"), 6)) {
286 xmlChar* source = xmlGetProp(child, BAD_CAST("source"));
287 ts.source = rc->getBitmap((const char*)source);
291 // Generate default tile types in range (m,n] where m is the last
292 // explicitly declared type and n is the number we require.
293 unsigned srcsz = ts.source.width() * ts.source.height();
294 unsigned long tilesz = (unsigned long)(ts.tiledim.x * ts.tiledim.y);
295 while (ts.defaults.size() != srcsz / tilesz) {
296 TileType tt = defaultTileType(ts.source,
297 ts.tiledim, (int)ts.defaults.size());
298 ts.defaults.push_back(tt);
301 tilesets.push_back(ts);
302 return true;
305 Area::TileType Area::defaultTileType(const Gosu::Bitmap source, coord_t tiledim,
306 int id)
308 unsigned x = (unsigned)((tiledim.x * id) % source.width());
309 unsigned y = (unsigned)((tiledim.y * id) / source.height() * tiledim.y); // ???
311 TileType tt;
312 Gosu::Image* img = rc->bitmapSection(source, x, y,
313 (unsigned)tiledim.x, (unsigned)tiledim.y, true);
314 tt.graphics.push_back(img);
315 tt.animated = false;
316 tt.ani_speed = 0.0;
317 tt.flags = 0x0;
318 return tt;
321 bool Area::processTileType(xmlNode* node, Tileset& ts)
325 <tile id="8">
326 <properties>
327 <property name="flags" value="nowalk"/>
328 <property name="onEnter" value="skid();speed(2)"/>
329 <property name="onLeave" value="undo()"/>
330 </properties>
331 </tile>
332 <tile id="14">
333 <properties>
334 <property name="animated" value="1"/>
335 <property name="size" value="2"/>
336 <property name="speed" value="2"/>
337 </properties>
338 </tile>
341 // Initialize a default TileType, we'll build on that.
342 TileType tt = defaultTileType(ts.source,
343 ts.tiledim, (int)ts.defaults.size());
345 xmlChar* idstr = xmlGetProp(node, BAD_CAST("id"));
346 unsigned id = (unsigned)atoi((const char*)idstr);
347 if (id != ts.defaults.size()) {
348 // XXX we need to know the Area we're loading...
349 Log::err("unknown area", std::string("expected TileType id ") +
350 itostr((long)ts.defaults.size()) + ", but got " + itostr(id));
351 return false;
354 xmlNode* child = node->xmlChildrenNode; // <properties>
355 child = child->xmlChildrenNode; // <property>
356 for (; child != NULL; child = child->next) {
357 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
358 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
359 if (!xmlStrncmp(name, BAD_CAST("flags"), 6)) {
360 tt.flags = splitTileFlags((const char*)value);
362 else if (!xmlStrncmp(name, BAD_CAST("onEnter"), 8)) {
363 // TODO events
365 else if (!xmlStrncmp(name, BAD_CAST("onLeave"), 8)) {
366 // TODO events
368 else if (!xmlStrncmp(name, BAD_CAST("animated"), 9)) {
369 tt.animated = parseBool((const char*)value);
371 else if (!xmlStrncmp(name, BAD_CAST("size"), 5)) {
372 // TODO animation
374 else if (!xmlStrncmp(name, BAD_CAST("speed"), 6)) {
375 tt.ani_speed = atof((const char*)value);
379 ts.defaults.push_back(tt);
380 return true;
383 bool Area::processLayer(xmlNode* node)
387 <layer name="Tiles0" width="5" height="5">
388 <properties>
390 </properties>
391 <data>
392 <tile gid="9"/>
393 <tile gid="9"/>
394 <tile gid="9"/>
396 <tile gid="3"/>
397 <tile gid="9"/>
398 <tile gid="9"/>
399 </data>
400 </layer>
403 xmlChar* width = xmlGetProp(node, BAD_CAST("width"));
404 xmlChar* height = xmlGetProp(node, BAD_CAST("height"));
405 int x = atoi((const char*)width);
406 int y = atoi((const char*)height);
408 if (dim.x != x || dim.y != y) {
409 // XXX we need to know the Area we're loading...
410 Log::err("unknown area", "layer x,y size != map x,y size");
411 return false;
414 xmlNode* child = node->xmlChildrenNode;
415 for (; child != NULL; child = child->next) {
416 if (!xmlStrncmp(child->name, BAD_CAST("properties"), 11)) {
417 if (!processLayerProperties(child))
418 return false;
420 else if (!xmlStrncmp(child->name, BAD_CAST("data"), 5)) {
421 if (!processLayerData(child))
422 return false;
425 return true;
428 bool Area::processLayerProperties(xmlNode* node)
432 <properties>
433 <property name="layer" value="0"/>
434 </properties>
437 xmlNode* child = node->xmlChildrenNode;
438 for (; child != NULL; child = child->next) {
439 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
440 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
441 if (!xmlStrncmp(name, BAD_CAST("layer"), 6)) {
442 int depth = atoi((const char*)value);
443 if (depth != dim.z) {
444 Log::err("unknown area", "invalid layer depth");
445 return false;
450 return true;
453 bool Area::processLayerData(xmlNode* node)
457 <data>
458 <tile gid="9"/>
459 <tile gid="9"/>
460 <tile gid="9"/>
462 <tile gid="3"/>
463 <tile gid="9"/>
464 <tile gid="9"/>
465 </data>
468 row_t row;
469 grid_t grid;
471 xmlNode* child = node->xmlChildrenNode;
472 for (int i = 1; child != NULL; i++, child = child->next) {
473 if (!xmlStrncmp(child->name, BAD_CAST("tile"), 5)) {
474 xmlChar* gidStr = xmlGetProp(child, BAD_CAST("gid"));
475 unsigned gid = (unsigned)atoi((const char*)gidStr)-1;
476 Tile* t = new Tile;
477 t->type = &tilesets[0].defaults[gid]; // XXX can only access first tileset
478 t->flags = 0x0;
479 t->door = NULL;
480 row.push_back(t);
481 if (row.size() % dim.x == 0) {
482 grid.push_back(row);
483 row.clear();
488 map.push_back(grid);
489 dim.z++;
490 return true;
493 bool Area::processObjectGroup(xmlNode* node)
497 <objectgroup name="Prop0" width="5" height="5">
498 <properties>
499 <property name="layer" value="0"/>
500 </properties>
501 <object name="tile2" type="Tile" gid="7" x="64" y="320">
502 <properties>
503 <property name="onEnter" value="speed(0.5)"/>
504 <property name="onLeave" value="undo()"/>
505 <property name="door" value="grassfield.area,1,1,0"/>
506 <property name="flags" value="npc_nowalk"/>
507 </properties>
508 </object>
509 </objectgroup>
512 xmlChar* width = xmlGetProp(node, BAD_CAST("width"));
513 xmlChar* height = xmlGetProp(node, BAD_CAST("height"));
514 int x = atoi((const char*)width);
515 int y = atoi((const char*)height);
517 int zpos = -1;
519 if (dim.x != x || dim.y != y) {
520 // XXX we need to know the Area we're loading...
521 Log::err("unknown area", "objectgroup x,y size != map x,y size");
522 return false;
525 xmlNode* child = node->xmlChildrenNode;
526 for (; child != NULL; child = child->next) {
527 if (!xmlStrncmp(child->name, BAD_CAST("properties"), 11)) {
528 if (!processObjectGroupProperties(child, &zpos))
529 return false;
531 else if (!xmlStrncmp(child->name, BAD_CAST("object"), 7)) {
532 if (zpos == -1 || !processObject(child, zpos))
533 return false;
537 return true;
540 bool Area::processObjectGroupProperties(xmlNode* node, int* zpos)
544 <properties>
545 <property name="layer" value="0"/>
546 </properties>
549 xmlNode* child = node->xmlChildrenNode;
550 for (; child != NULL; child = child->next) {
551 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
552 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
553 if (!xmlStrncmp(name, BAD_CAST("layer"), 6)) {
554 int layer = atoi((const char*)value);
555 if (0 < layer || layer >= (int)dim.z) {
556 // XXX we need to know the Area we're loading...
557 Log::err("unknown area",
558 "objectgroup must correspond with layer"
560 return false;
562 *zpos = layer;
565 return true;
568 bool Area::processObject(xmlNode* node, int zpos)
572 <object name="tile2" type="Tile" gid="7" x="64" y="320">
573 <properties>
574 <property name="onEnter" value="speed(0.5)"/>
575 <property name="onLeave" value="undo()"/>
576 <property name="door" value="grassfield.area,1,1,0"/>
577 <property name="flags" value="npc_nowalk"/>
578 </properties>
579 </object>
582 xmlChar* type = xmlGetProp(node, BAD_CAST("type"));
583 if (xmlStrncmp(type, BAD_CAST("Tile"), 5)) {
584 Log::err("unknown area", "object type must be Tile");
585 return false;
588 xmlChar* xStr = xmlGetProp(node, BAD_CAST("x"));
589 xmlChar* yStr = xmlGetProp(node, BAD_CAST("y"));
590 // XXX we ignore the object gid... is that okay?
592 // wouldn't have to access tilesets if we had tiledim ourselves
593 long x = atol((const char*)xStr) / tilesets[0].tiledim.x;
594 long y = atol((const char*)yStr) / tilesets[0].tiledim.y;
595 y = y - 1; // bug in tiled? y is 1 too high
597 // We know which Tile is being talked about now... yay
598 Tile* t = map[zpos][y][x];
600 xmlNode* child = node->xmlChildrenNode; // <properties>
601 child = child->xmlChildrenNode; // <property>
602 for (; child != NULL; child = child->next) {
603 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
604 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
605 if (!xmlStrncmp(name, BAD_CAST("flags"), 6)) {
606 t->flags = splitTileFlags((const char*)value);
608 else if (!xmlStrncmp(name, BAD_CAST("onEnter"), 8)) {
609 // TODO events
611 else if (!xmlStrncmp(name, BAD_CAST("onLeave"), 8)) {
612 // TODO events
614 else if (!xmlStrncmp(name, BAD_CAST("door"), 5)) {
615 t->door = parseDoor((const char*)value);
616 t->flags |= npc_nowalk;
619 return true;
622 unsigned Area::splitTileFlags(const std::string strOfFlags)
624 std::vector<std::string> strs;
625 strs = splitStr(strOfFlags, ",");
627 unsigned flags = 0x0;
628 BOOST_FOREACH(std::string str, strs)
630 // TODO: reimplement comparisons as a hash table
631 if (str == "nowalk") {
632 flags |= nowalk;
635 return flags;
638 Area::Door* Area::parseDoor(const std::string dest)
640 std::vector<std::string> strs;
641 strs = splitStr(dest, ",");
643 // TODO: verify the validity of the input string... it's coming from
644 // user land
645 Door* door = new Door;
646 door->area = strs[0];
647 door->coord.x = atol(strs[1].c_str());
648 door->coord.y = atol(strs[2].c_str());
649 door->coord.z = atol(strs[3].c_str());
650 return door;
653 coord_t Area::getDimensions() const
655 return dim;
658 Area::Tile* Area::getTile(coord_t c)
660 return map[c.z][c.y][c.x];