TODO update regarding door activation
[Tsunagari.git] / src / area.cpp
blob90704bc1bf74b57d151be1412f41ba31b28966a9
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, World* world, Entity* player, const std::string& descriptor)
25 : rc(rc), world(world), player(player), descriptor(descriptor)
27 dim.z = 0;
30 Area::~Area()
34 bool Area::init()
36 if (!processDescriptor()) // Try to load in descriptor.
37 return false;
38 return true;
41 void Area::buttonDown(const Gosu::Button btn)
43 bool attemptingMove = false;
44 coord_t posMove;
46 if (btn == Gosu::kbRight) {
47 posMove = coord(1, 0, 0);
48 attemptingMove = true;
50 else if (btn == Gosu::kbLeft) {
51 posMove = coord(-1, 0, 0);
52 attemptingMove = true;
54 else if (btn == Gosu::kbUp) {
55 posMove = coord(0, -1, 0);
56 attemptingMove = true;
58 else if (btn == Gosu::kbDown) {
59 posMove = coord(0, 1, 0);
60 attemptingMove = true;
63 if (attemptingMove) {
64 coord_t newCoord = player->getCoordsByTile();
65 newCoord.x += posMove.x;
66 newCoord.y += posMove.y;
67 newCoord.z += posMove.z;
68 Tile* dest = getTile(newCoord);
69 if ((dest->flags & player_nowalk) != 0 ||
70 (dest->type->flags & player_nowalk) != 0) {
71 // The tile we're trying to move onto is set as nowalk
72 // for the player. Stop here.
73 return;
75 if (dest->door) {
76 world->loadArea(dest->door->area, dest->door->coord);
78 else {
79 player->moveByTile(posMove);
84 void Area::draw()
86 GameWindow* window = GameWindow::getWindow();
87 Gosu::Graphics* graphics = &window->graphics();
88 Gosu::Transform trans = translateCoords();
89 graphics->pushTransform(trans);
91 for (unsigned int layer = 0; layer != map.size(); layer++)
93 grid_t grid = map[layer];
94 for (unsigned int y = 0; y != grid.size(); y++)
96 row_t row = grid[y];
97 for (unsigned int x = 0; x != row.size(); x++)
99 // TODO support animations
100 Tile* tile = row[x];
101 Gosu::Image* img = tile->type->graphics[0];
102 img->draw(x*img->width(), y*img->height(), 0);
106 player->draw();
108 graphics->popTransform();
111 static double bound(double low, double x, double high)
113 if (low > x)
114 x = low;
115 if (x > high)
116 x = high;
117 return x;
120 static double center(double w, double g, double p)
122 return w>g ? (w-g)/2.0 : bound(w-g, w/2.0-p, 0);
125 Gosu::Transform Area::translateCoords()
127 GameWindow* window = GameWindow::getWindow();
128 Gosu::Graphics* graphics = &window->graphics();
130 // FIXME: horrible
131 double tileSize = map[0][0][0]->type->graphics[0]->width();
132 double windowWidth = graphics->width() / tileSize;
133 double windowHeight = graphics->height() / tileSize;
134 double gridWidth = dim.x;
135 double gridHeight = dim.y;
136 double playerX = player->getCoordsByPixel().x / tileSize + 0.5;
137 double playerY = player->getCoordsByPixel().y / tileSize + 0.5;
139 coord_t c;
140 c.x = center(windowWidth, gridWidth, playerX) * tileSize;
141 c.y = center(windowHeight, gridHeight, playerY) * tileSize;
143 Gosu::Transform trans = Gosu::translate(c.x, c.y);
144 return trans;
147 bool Area::needsRedraw() const
149 return player->needsRedraw();
152 bool Area::processDescriptor()
154 xmlDoc* doc = rc->getXMLDoc(descriptor);
155 if (!doc)
156 return false;
158 // use RAII to ensure doc is freed
159 boost::shared_ptr<void> alwaysFreeTheDoc(doc, xmlFreeDoc);
161 // Iterate and process children of <map>
162 xmlNode* root = xmlDocGetRootElement(doc); // <map> element
164 xmlChar* width = xmlGetProp(root, BAD_CAST("width"));
165 xmlChar* height = xmlGetProp(root, BAD_CAST("height"));
166 dim.x = atol((const char*)width);
167 dim.y = atol((const char*)height);
169 xmlNode* child = root->xmlChildrenNode;
170 for (; child != NULL; child = child->next) {
171 if (!xmlStrncmp(child->name, BAD_CAST("properties"), 11)) {
172 if (!processMapProperties(child))
173 return false;
175 else if (!xmlStrncmp(child->name, BAD_CAST("tileset"), 8)) {
176 if (!processTileset(child))
177 return false;
179 else if (!xmlStrncmp(child->name, BAD_CAST("layer"), 6)) {
180 if (!processLayer(child))
181 return false;
183 else if (!xmlStrncmp(child->name, BAD_CAST("objectgroup"), 12)) {
184 if (!processObjectGroup(child))
185 return false;
189 return true;
192 bool Area::processMapProperties(xmlNode* node)
196 <properties>
197 <property name="areaspec" value="1"/>
198 <property name="author" value="Michael D. Reiley"/>
199 <property name="name" value="Baby's First Area"/>
200 <property name="music_loop" value="true"/>
201 <property name="music_main" value="wind.music"/>
202 <property name="onLoad" value="babysfirst_init()"/>
203 <property name="scripts" value="areainits.event,test.event"/>
204 </properties>
207 xmlNode* child = node->xmlChildrenNode;
208 for (; child != NULL; child = child->next) {
209 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
210 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
211 if (!xmlStrncmp(name, BAD_CAST("author"), 7))
212 author = (const char*)value;
213 else if (!xmlStrncmp(name, BAD_CAST("name"), 5))
214 this->name = (const char*)value;
215 else if (!xmlStrncmp(name, BAD_CAST("music_loop"), 11))
216 main.loop = parseBool((const char*)value);
217 else if (!xmlStrncmp(name, BAD_CAST("music_main"), 11))
218 main.filename = (const char*)value;
219 else if (!xmlStrncmp(name, BAD_CAST("onLoad"), 7))
220 onLoadEvents = (const char*)value;
221 else if (!xmlStrncmp(name, BAD_CAST("scripts"), 8))
222 scripts = (const char*)value; // TODO split(), load
224 return true;
227 bool Area::processTileset(xmlNode* node)
231 <tileset firstgid="1" name="tiles.sheet" tilewidth="64" tileheight="64">
232 <image source="tiles.sheet" width="256" height="256"/>
233 <tile id="14">
235 </tile>
236 </tileset>
238 Tileset ts;
239 xmlChar* width = xmlGetProp(node, BAD_CAST("tilewidth"));
240 xmlChar* height = xmlGetProp(node, BAD_CAST("tileheight"));
241 ts.tiledim.x = atol((const char*)width);
242 ts.tiledim.y = atol((const char*)height);
244 xmlNode* child = node->xmlChildrenNode;
245 for (; child != NULL; child = child->next) {
246 if (!xmlStrncmp(child->name, BAD_CAST("tile"), 5)) {
247 xmlChar* idstr = xmlGetProp(child, BAD_CAST("id"));
248 unsigned id = atol((const char*)idstr);
250 // Undeclared TileTypes have default properties.
251 while (ts.defaults.size() != id) {
252 TileType tt = defaultTileType(ts.source,
253 ts.tiledim, ts.defaults.size());
254 ts.defaults.push_back(tt);
257 // Handle explicit TileType
258 if (!processTileType(child, ts))
259 return false;
261 else if (!xmlStrncmp(child->name, BAD_CAST("image"), 6)) {
262 xmlChar* source = xmlGetProp(child, BAD_CAST("source"));
263 ts.source = rc->getBitmap((const char*)source);
267 // Generate default tile types in range (m,n] where m is the last
268 // explicitly declared type and n is the number we require.
269 unsigned srcsz = ts.source.width() * ts.source.height();
270 unsigned tilesz = ts.tiledim.x * ts.tiledim.y;
271 while (ts.defaults.size() != srcsz / tilesz) {
272 TileType tt = defaultTileType(ts.source,
273 ts.tiledim, ts.defaults.size());
274 ts.defaults.push_back(tt);
277 tilesets.push_back(ts);
278 return true;
281 Area::TileType Area::defaultTileType(const Gosu::Bitmap source, coord_t tiledim,
282 int id)
284 int x = (tiledim.x * id) % source.width();
285 int y = (tiledim.y * id) / source.height() * tiledim.y; // ???
287 TileType tt;
288 Gosu::Image* img = rc->bitmapSection(source, x, y,
289 tiledim.x, tiledim.y, true);
290 tt.graphics.push_back(img);
291 tt.animated = false;
292 tt.ani_speed = 0.0;
293 tt.flags = 0x0;
294 return tt;
297 bool Area::processTileType(xmlNode* node, Tileset& ts)
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 </properties>
307 </tile>
308 <tile id="14">
309 <properties>
310 <property name="animated" value="1"/>
311 <property name="size" value="2"/>
312 <property name="speed" value="2"/>
313 </properties>
314 </tile>
317 // Initialize a default TileType, we'll build on that.
318 TileType tt = defaultTileType(ts.source,
319 ts.tiledim, ts.defaults.size());
321 xmlChar* idstr = xmlGetProp(node, BAD_CAST("id"));
322 unsigned id = atol((const char*)idstr);
323 if (id != ts.defaults.size()) {
324 // XXX we need to know the Area we're loading...
325 Log::err("unknown area", std::string("expected TileType id ") +
326 itostr(ts.defaults.size()) + ", but got " + itostr(id));
327 return false;
330 xmlNode* child = node->xmlChildrenNode; // <properties>
331 child = child->xmlChildrenNode; // <property>
332 for (; child != NULL; child = child->next) {
333 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
334 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
335 if (!xmlStrncmp(name, BAD_CAST("flags"), 6)) {
336 tt.flags = splitTileFlags((const char*)value);
338 else if (!xmlStrncmp(name, BAD_CAST("onEnter"), 8)) {
339 // TODO events
341 else if (!xmlStrncmp(name, BAD_CAST("onLeave"), 8)) {
342 // TODO events
344 else if (!xmlStrncmp(name, BAD_CAST("animated"), 9)) {
345 tt.animated = parseBool((const char*)value);
347 else if (!xmlStrncmp(name, BAD_CAST("size"), 5)) {
348 // TODO animation
350 else if (!xmlStrncmp(name, BAD_CAST("speed"), 6)) {
351 tt.ani_speed = atol((const char*)value);
355 ts.defaults.push_back(tt);
356 return true;
359 bool Area::processLayer(xmlNode* node)
363 <layer name="Tiles0" width="5" height="5">
364 <properties>
366 </properties>
367 <data>
368 <tile gid="9"/>
369 <tile gid="9"/>
370 <tile gid="9"/>
372 <tile gid="3"/>
373 <tile gid="9"/>
374 <tile gid="9"/>
375 </data>
376 </layer>
379 xmlChar* width = xmlGetProp(node, BAD_CAST("width"));
380 xmlChar* height = xmlGetProp(node, BAD_CAST("height"));
381 int x = atol((const char*)width);
382 int y = atol((const char*)height);
384 if (dim.x != x || dim.y != y) {
385 // XXX we need to know the Area we're loading...
386 Log::err("unknown area", "layer x,y size != map x,y size");
387 return false;
390 xmlNode* child = node->xmlChildrenNode;
391 for (; child != NULL; child = child->next) {
392 if (!xmlStrncmp(child->name, BAD_CAST("properties"), 11)) {
393 if (!processLayerProperties(child))
394 return false;
396 else if (!xmlStrncmp(child->name, BAD_CAST("data"), 5)) {
397 if (!processLayerData(child))
398 return false;
401 return true;
404 bool Area::processLayerProperties(xmlNode* node)
408 <properties>
409 <property name="layer" value="0"/>
410 </properties>
413 xmlNode* child = node->xmlChildrenNode;
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("layer"), 6)) {
418 int depth = atol((const char*)value);
419 if (depth != dim.z) {
420 Log::err("unknown area", "invalid layer depth");
421 return false;
426 return true;
429 bool Area::processLayerData(xmlNode* node)
433 <data>
434 <tile gid="9"/>
435 <tile gid="9"/>
436 <tile gid="9"/>
438 <tile gid="3"/>
439 <tile gid="9"/>
440 <tile gid="9"/>
441 </data>
444 row_t row;
445 grid_t grid;
447 xmlNode* child = node->xmlChildrenNode;
448 for (int i = 1; child != NULL; i++, child = child->next) {
449 if (!xmlStrncmp(child->name, BAD_CAST("tile"), 5)) {
450 xmlChar* gidStr = xmlGetProp(child, BAD_CAST("gid"));
451 unsigned gid = atol((const char*)gidStr)-1;
452 Tile* t = new Tile;
453 t->type = &tilesets[0].defaults[gid]; // XXX can only access first tileset
454 t->flags = 0x0;
455 t->door = NULL;
456 row.push_back(t);
457 if (row.size() % dim.x == 0) {
458 grid.push_back(row);
459 row.clear();
464 map.push_back(grid);
465 dim.z++;
466 return true;
469 bool Area::processObjectGroup(xmlNode* node)
473 <objectgroup name="Prop0" width="5" height="5">
474 <properties>
475 <property name="layer" value="0"/>
476 </properties>
477 <object name="tile2" type="Tile" gid="7" x="64" y="320">
478 <properties>
479 <property name="onEnter" value="speed(0.5)"/>
480 <property name="onLeave" value="undo()"/>
481 <property name="door" value="grassfield.area,1,1,0"/>
482 <property name="flags" value="npc_nowalk"/>
483 </properties>
484 </object>
485 </objectgroup>
488 xmlChar* width = xmlGetProp(node, BAD_CAST("width"));
489 xmlChar* height = xmlGetProp(node, BAD_CAST("height"));
490 int x = atol((const char*)width);
491 int y = atol((const char*)height);
493 int zpos = -1;
495 if (dim.x != x || dim.y != y) {
496 // XXX we need to know the Area we're loading...
497 Log::err("unknown area", "objectgroup x,y size != map x,y size");
498 return false;
501 xmlNode* child = node->xmlChildrenNode;
502 for (; child != NULL; child = child->next) {
503 if (!xmlStrncmp(child->name, BAD_CAST("properties"), 11)) {
504 if (!processObjectGroupProperties(child, &zpos))
505 return false;
507 else if (!xmlStrncmp(child->name, BAD_CAST("object"), 7)) {
508 if (zpos == -1 || !processObject(child, zpos))
509 return false;
513 return true;
516 bool Area::processObjectGroupProperties(xmlNode* node, int* zpos)
520 <properties>
521 <property name="layer" value="0"/>
522 </properties>
525 xmlNode* child = node->xmlChildrenNode;
526 for (; child != NULL; child = child->next) {
527 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
528 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
529 if (!xmlStrncmp(name, BAD_CAST("layer"), 6)) {
530 int layer = atol((const char*)value);
531 if (0 < layer || layer >= (int)dim.z) {
532 // XXX we need to know the Area we're loading...
533 Log::err("unknown area",
534 "objectgroup must correspond with layer"
536 return false;
538 *zpos = layer;
541 return true;
544 bool Area::processObject(xmlNode* node, int zpos)
548 <object name="tile2" type="Tile" gid="7" x="64" y="320">
549 <properties>
550 <property name="onEnter" value="speed(0.5)"/>
551 <property name="onLeave" value="undo()"/>
552 <property name="door" value="grassfield.area,1,1,0"/>
553 <property name="flags" value="npc_nowalk"/>
554 </properties>
555 </object>
558 xmlChar* type = xmlGetProp(node, BAD_CAST("type"));
559 if (xmlStrncmp(type, BAD_CAST("Tile"), 5)) {
560 Log::err("unknown area", "object type must be Tile");
561 return false;
564 xmlChar* xStr = xmlGetProp(node, BAD_CAST("x"));
565 xmlChar* yStr = xmlGetProp(node, BAD_CAST("y"));
566 // XXX we ignore the object gid... is that okay?
568 // wouldn't have to access tilesets if we had tiledim ourselves
569 int x = atol((const char*)xStr) / tilesets[0].tiledim.x;
570 int y = atol((const char*)yStr) / tilesets[0].tiledim.y;
571 y = y - 1; // bug in tiled? y is 1 too high
573 // We know which Tile is being talked about now... yay
574 Tile* t = map[zpos][y][x];
576 xmlNode* child = node->xmlChildrenNode; // <properties>
577 child = child->xmlChildrenNode; // <property>
578 for (; child != NULL; child = child->next) {
579 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
580 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
581 if (!xmlStrncmp(name, BAD_CAST("flags"), 6)) {
582 t->flags = splitTileFlags((const char*)value);
584 else if (!xmlStrncmp(name, BAD_CAST("onEnter"), 8)) {
585 // TODO events
587 else if (!xmlStrncmp(name, BAD_CAST("onLeave"), 8)) {
588 // TODO events
590 else if (!xmlStrncmp(name, BAD_CAST("door"), 5)) {
591 t->door = parseDoor((const char*)value);
594 return true;
597 unsigned Area::splitTileFlags(const std::string strOfFlags)
599 std::vector<std::string> strs;
600 strs = splitStr(strOfFlags, ",");
602 unsigned flags = 0x0;
603 BOOST_FOREACH(std::string str, strs)
605 // TODO: reimplement comparisons as a hash table
606 if (str == "nowalk") {
607 flags |= nowalk;
610 return flags;
613 Area::Door* Area::parseDoor(const std::string dest)
615 std::vector<std::string> strs;
616 strs = splitStr(dest, ",");
618 // TODO: verify the validity of the input string... it's coming from
619 // user land
620 Door* door = new Door;
621 door->area = strs[0];
622 door->coord.x = atol(strs[1].c_str());
623 door->coord.y = atol(strs[2].c_str());
624 door->coord.z = atol(strs[3].c_str());
625 return door;
628 coord_t Area::getDimensions() const
630 return dim;
633 Area::Tile* Area::getTile(coord_t c)
635 return map[c.z][c.y][c.x];