area: remove extraneous movement code
[Tsunagari.git] / src / area.cpp
blob687c806d4e2cc4db696fd13e69ab0dc9e4fa17fe
1 /******************************
2 ** Tsunagari Tile Engine **
3 ** area.cpp **
4 ** Copyright 2011 OmegaSDG **
5 ******************************/
7 #include <boost/foreach.hpp>
8 #include <boost/shared_ptr.hpp>
9 #include <Gosu/Audio.hpp>
10 #include <Gosu/Graphics.hpp>
11 #include <Gosu/Image.hpp>
13 #include "area.h"
14 #include "common.h"
15 #include "entity.h"
16 #include "log.h"
17 #include "resourcer.h"
18 #include "window.h"
19 #include "world.h"
21 /* NOTE: Tileset tiles start counting their positions from 0, while layer tiles
22 start counting from 1. I can't imagine why the author did this, but we
23 have to take it into account.
26 Area::Area(Resourcer* rc,
27 World* world,
28 Player* player,
29 const std::string& descriptor)
30 : rc(rc), world(world), player(player), descriptor(descriptor),
31 onIntro(false)
33 dim.x = dim.y = dim.z = 0;
36 Area::~Area()
38 if (musicInst && musicInst->playing())
39 musicInst->stop();
41 // Delete each Tile. If a Tile has an allocated Door struct, delete
42 // that as well.
43 BOOST_FOREACH(grid_t grid, map) {
44 BOOST_FOREACH(row_t row, grid) {
45 BOOST_FOREACH(Tile* tile, row) {
46 delete tile->door;
47 delete tile;
52 // Each Area owns its own Tileset objects. Delete tileset graphics.
53 BOOST_FOREACH(Tileset tileset, tilesets) {
54 delete tileset.source;
55 BOOST_FOREACH(TileType type, tileset.defaults) {
56 BOOST_FOREACH(Gosu::Image* img, type.graphics) {
57 delete img;
59 // TODO: delete TileEvents when we start using them
64 bool Area::init()
66 if (!processDescriptor())
67 return false;
69 if (introMusic) {
70 musicInst.reset(new Gosu::SampleInstance(introMusic->play(1, 1, false)));
71 onIntro = true;
73 else if (mainMusic)
74 musicInst.reset(new Gosu::SampleInstance(mainMusic->play(1, 1, true)));
75 return true;
78 void Area::buttonDown(const Gosu::Button btn)
80 bool attemptingMove = false;
81 coord_t posMove;
83 if (btn == Gosu::kbRight) {
84 posMove = coord(1, 0, 0);
85 attemptingMove = true;
87 else if (btn == Gosu::kbLeft) {
88 posMove = coord(-1, 0, 0);
89 attemptingMove = true;
91 else if (btn == Gosu::kbUp) {
92 posMove = coord(0, -1, 0);
93 attemptingMove = true;
95 else if (btn == Gosu::kbDown) {
96 posMove = coord(0, 1, 0);
97 attemptingMove = true;
100 if (attemptingMove)
101 player->moveByTile(posMove);
104 void Area::draw()
106 GameWindow* window = GameWindow::getWindow();
107 Gosu::Graphics* graphics = &window->graphics();
108 Gosu::Transform trans = translateCoords();
109 graphics->pushTransform(trans);
111 for (unsigned layer = 0; layer != map.size(); layer++)
113 grid_t grid = map[layer];
114 for (unsigned y = 0; y != grid.size(); y++)
116 row_t row = grid[y];
117 for (unsigned x = 0; x != row.size(); x++)
119 // TODO support animations
120 Tile* tile = row[x];
121 Gosu::Image* img = tile->type->graphics[0];
122 img->draw(x*img->width(), y*img->height(), 0);
126 player->draw();
128 graphics->popTransform();
131 bool Area::needsRedraw() const
133 return player->needsRedraw();
136 void Area::update()
138 if (onIntro && !musicInst->playing()) {
139 onIntro = false;
140 musicInst.reset(new Gosu::SampleInstance(mainMusic->play(1, 1, true)));
144 //! Returns the number closest to x within the range [low, high].
146 \param low Lowest possible return.
147 \param x Value to be bounded.
148 \param high Highest possible return.
149 \return A number close to x.
150 \sa center() and translateCoords()
152 static double bound(double low, double x, double high)
154 if (low > x)
155 x = low;
156 if (x > high)
157 x = high;
158 return x;
161 static double center(double w, double g, double p)
163 return w>g ? (w-g)/2.0 : bound(w-g, w/2.0-p, 0);
166 Gosu::Transform Area::translateCoords()
168 GameWindow* window = GameWindow::getWindow();
169 Gosu::Graphics* graphics = &window->graphics();
171 double tileWidth = (double)tilesets[0].tiledim.x;
172 double tileHeight = (double)tilesets[0].tiledim.y;
173 double windowWidth = (double)graphics->width() / tileWidth;
174 double windowHeight = (double)graphics->height() / tileHeight;
175 double gridWidth = (double)dim.x;
176 double gridHeight = (double)dim.y;
177 double playerX = (double)player->getCoordsByPixel().x / tileWidth + 0.5;
178 double playerY = (double)player->getCoordsByPixel().y / tileHeight + 0.5;
180 coord_t c;
181 c.x = (long)(center(windowWidth, gridWidth, playerX) * tileWidth);
182 c.y = (long)(center(windowHeight, gridHeight, playerY) * tileHeight);
184 Gosu::Transform trans = Gosu::translate((double)c.x, (double)c.y);
185 return trans;
188 bool Area::processDescriptor()
190 XMLDocRef doc = rc->getXMLDoc(descriptor, "dtd/area.dtd");
191 if (!doc)
192 return false;
194 // Iterate and process children of <map>
195 xmlNode* root = xmlDocGetRootElement(doc.get()); // <map> element
197 xmlChar* width = xmlGetProp(root, BAD_CAST("width"));
198 xmlChar* height = xmlGetProp(root, BAD_CAST("height"));
199 dim.x = atol((const char*)width);
200 dim.y = atol((const char*)height);
202 xmlNode* child = root->xmlChildrenNode;
203 for (; child != NULL; child = child->next) {
204 if (!xmlStrncmp(child->name, BAD_CAST("properties"), 11)) {
205 if (!processMapProperties(child))
206 return false;
208 else if (!xmlStrncmp(child->name, BAD_CAST("tileset"), 8)) {
209 if (!processTileset(child))
210 return false;
212 else if (!xmlStrncmp(child->name, BAD_CAST("layer"), 6)) {
213 if (!processLayer(child))
214 return false;
216 else if (!xmlStrncmp(child->name, BAD_CAST("objectgroup"), 12)) {
217 if (!processObjectGroup(child))
218 return false;
222 return true;
225 bool Area::processMapProperties(xmlNode* node)
229 <properties>
230 <property name="areaspec" value="1"/>
231 <property name="author" value="Michael D. Reiley"/>
232 <property name="name" value="Baby's First Area"/>
233 <property name="intro_music" value="intro.music"/>
234 <property name="main_music" value="wind.music"/>
235 <property name="onLoad" value="babysfirst_init()"/>
236 <property name="scripts" value="areainits.event,test.event"/>
237 </properties>
240 xmlNode* child = node->xmlChildrenNode;
241 for (; child != NULL; child = child->next) {
242 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
243 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
244 if (!xmlStrncmp(name, BAD_CAST("author"), 7))
245 author = (const char*)value;
246 else if (!xmlStrncmp(name, BAD_CAST("name"), 5))
247 this->name = (const char*)value;
248 else if (!xmlStrncmp(name, BAD_CAST("intro_music"), 12))
249 introMusic = rc->getSample((const char*)value);
250 else if (!xmlStrncmp(name, BAD_CAST("main_music"), 11))
251 mainMusic = rc->getSample((const char*)value);
252 else if (!xmlStrncmp(name, BAD_CAST("onLoad"), 7))
253 onLoadEvents = (const char*)value;
254 else if (!xmlStrncmp(name, BAD_CAST("scripts"), 8))
255 scripts = (const char*)value; // TODO split(), load
257 return true;
260 bool Area::processTileset(xmlNode* node)
264 <tileset firstgid="1" name="tiles.sheet" tilewidth="64" tileheight="64">
265 <image source="tiles.sheet" width="256" height="256"/>
266 <tile id="14">
268 </tile>
269 </tileset>
271 Tileset ts;
272 ts.source = new Gosu::Bitmap;
273 xmlChar* width = xmlGetProp(node, BAD_CAST("tilewidth"));
274 xmlChar* height = xmlGetProp(node, BAD_CAST("tileheight"));
275 ts.tiledim.x = atol((const char*)width);
276 ts.tiledim.y = atol((const char*)height);
278 xmlNode* child = node->xmlChildrenNode;
279 for (; child != NULL; child = child->next) {
280 if (!xmlStrncmp(child->name, BAD_CAST("tile"), 5)) {
281 xmlChar* idstr = xmlGetProp(child, BAD_CAST("id"));
282 unsigned id = (unsigned)atoi((const char*)idstr);
284 // Undeclared TileTypes have default properties.
285 while (ts.defaults.size() != id) {
286 TileType tt = defaultTileType(ts.source,
287 ts.tiledim, (int)ts.defaults.size());
288 ts.defaults.push_back(tt);
291 // Handle explicit TileType
292 if (!processTileType(child, ts))
293 return false;
295 else if (!xmlStrncmp(child->name, BAD_CAST("image"), 6)) {
296 xmlChar* source = xmlGetProp(child, BAD_CAST("source"));
297 rc->getBitmap(*ts.source, (const char*)source);
301 // Generate default tile types in range (m,n] where m is the last
302 // explicitly declared type and n is the number we require.
303 unsigned srcsz = ts.source->width() * ts.source->height();
304 unsigned long tilesz = (unsigned long)(ts.tiledim.x * ts.tiledim.y);
305 while (ts.defaults.size() != srcsz / tilesz) {
306 TileType tt = defaultTileType(ts.source,
307 ts.tiledim, (int)ts.defaults.size());
308 ts.defaults.push_back(tt);
311 tilesets.push_back(ts);
312 return true;
315 Area::TileType Area::defaultTileType(const Gosu::Bitmap* source,
316 coord_t tiledim, int id)
318 unsigned x = (unsigned)((tiledim.x * id) % source->width());
319 unsigned y = (unsigned)((tiledim.y * id) / source->width() * tiledim.y); // ???
321 TileType tt;
322 Gosu::Image* img = rc->bitmapSection(*source, x, y,
323 (unsigned)tiledim.x, (unsigned)tiledim.y, true);
324 tt.graphics.push_back(img);
325 tt.animated = false;
326 tt.ani_speed = 0.0;
327 tt.flags = 0x0;
328 return tt;
331 bool Area::processTileType(xmlNode* node, Tileset& ts)
335 <tile id="8">
336 <properties>
337 <property name="flags" value="nowalk"/>
338 <property name="onEnter" value="skid();speed(2)"/>
339 <property name="onLeave" value="undo()"/>
340 </properties>
341 </tile>
342 <tile id="14">
343 <properties>
344 <property name="animated" value="1"/>
345 <property name="size" value="2"/>
346 <property name="speed" value="2"/>
347 </properties>
348 </tile>
351 // Initialize a default TileType, we'll build on that.
352 TileType tt = defaultTileType(ts.source,
353 ts.tiledim, (int)ts.defaults.size());
355 xmlChar* idstr = xmlGetProp(node, BAD_CAST("id"));
356 unsigned id = (unsigned)atoi((const char*)idstr);
357 if (id != ts.defaults.size()) {
358 // XXX we need to know the Area we're loading...
359 Log::err("unknown area", std::string("expected TileType id ") +
360 itostr((long)ts.defaults.size()) + ", but got " + itostr(id));
361 return false;
364 xmlNode* child = node->xmlChildrenNode; // <properties>
365 child = child->xmlChildrenNode; // <property>
366 for (; child != NULL; child = child->next) {
367 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
368 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
369 if (!xmlStrncmp(name, BAD_CAST("flags"), 6)) {
370 tt.flags = splitTileFlags((const char*)value);
372 else if (!xmlStrncmp(name, BAD_CAST("onEnter"), 8)) {
373 // TODO events
375 else if (!xmlStrncmp(name, BAD_CAST("onLeave"), 8)) {
376 // TODO events
378 else if (!xmlStrncmp(name, BAD_CAST("animated"), 9)) {
379 tt.animated = parseBool((const char*)value);
381 else if (!xmlStrncmp(name, BAD_CAST("size"), 5)) {
382 // TODO animation
384 else if (!xmlStrncmp(name, BAD_CAST("speed"), 6)) {
385 tt.ani_speed = atof((const char*)value);
389 ts.defaults.push_back(tt);
390 return true;
393 bool Area::processLayer(xmlNode* node)
397 <layer name="Tiles0" width="5" height="5">
398 <properties>
400 </properties>
401 <data>
402 <tile gid="9"/>
403 <tile gid="9"/>
404 <tile gid="9"/>
406 <tile gid="3"/>
407 <tile gid="9"/>
408 <tile gid="9"/>
409 </data>
410 </layer>
413 xmlChar* width = xmlGetProp(node, BAD_CAST("width"));
414 xmlChar* height = xmlGetProp(node, BAD_CAST("height"));
415 int x = atoi((const char*)width);
416 int y = atoi((const char*)height);
418 if (dim.x != x || dim.y != y) {
419 // XXX we need to know the Area we're loading...
420 Log::err("unknown area", "layer x,y size != map x,y size");
421 return false;
424 xmlNode* child = node->xmlChildrenNode;
425 for (; child != NULL; child = child->next) {
426 if (!xmlStrncmp(child->name, BAD_CAST("properties"), 11)) {
427 if (!processLayerProperties(child))
428 return false;
430 else if (!xmlStrncmp(child->name, BAD_CAST("data"), 5)) {
431 if (!processLayerData(child))
432 return false;
435 return true;
438 bool Area::processLayerProperties(xmlNode* node)
442 <properties>
443 <property name="layer" value="0"/>
444 </properties>
447 xmlNode* child = node->xmlChildrenNode;
448 for (; child != NULL; child = child->next) {
449 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
450 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
451 if (!xmlStrncmp(name, BAD_CAST("layer"), 6)) {
452 int depth = atoi((const char*)value);
453 if (depth != dim.z) {
454 Log::err("unknown area", "invalid layer depth");
455 return false;
460 return true;
463 bool Area::processLayerData(xmlNode* node)
467 <data>
468 <tile gid="9"/>
469 <tile gid="9"/>
470 <tile gid="9"/>
472 <tile gid="3"/>
473 <tile gid="9"/>
474 <tile gid="9"/>
475 </data>
478 row_t row;
479 grid_t grid;
481 row.reserve(dim.x);
482 grid.reserve(dim.y);
484 xmlNode* child = node->xmlChildrenNode;
485 for (int i = 1; child != NULL; i++, child = child->next) {
486 if (!xmlStrncmp(child->name, BAD_CAST("tile"), 5)) {
487 xmlChar* gidStr = xmlGetProp(child, BAD_CAST("gid"));
488 unsigned gid = (unsigned)atoi((const char*)gidStr)-1;
489 Tile* t = new Tile;
490 t->type = &tilesets[0].defaults[gid]; // XXX can only access first tileset
491 t->flags = 0x0;
492 t->door = NULL;
493 row.push_back(t);
494 if (row.size() % dim.x == 0) {
495 grid.push_back(row);
496 row.clear();
497 row.reserve(dim.x);
502 map.push_back(grid);
503 dim.z++;
504 return true;
507 bool Area::processObjectGroup(xmlNode* node)
511 <objectgroup name="Prop0" width="5" height="5">
512 <properties>
513 <property name="layer" value="0"/>
514 </properties>
515 <object name="tile2" type="Tile" gid="7" x="64" y="320">
516 <properties>
517 <property name="onEnter" value="speed(0.5)"/>
518 <property name="onLeave" value="undo()"/>
519 <property name="door" value="grassfield.area,1,1,0"/>
520 <property name="flags" value="npc_nowalk"/>
521 </properties>
522 </object>
523 </objectgroup>
526 xmlChar* width = xmlGetProp(node, BAD_CAST("width"));
527 xmlChar* height = xmlGetProp(node, BAD_CAST("height"));
528 int x = atoi((const char*)width);
529 int y = atoi((const char*)height);
531 int zpos = -1;
533 if (dim.x != x || dim.y != y) {
534 // XXX we need to know the Area we're loading...
535 Log::err("unknown area", "objectgroup x,y size != map x,y size");
536 return false;
539 xmlNode* child = node->xmlChildrenNode;
540 for (; child != NULL; child = child->next) {
541 if (!xmlStrncmp(child->name, BAD_CAST("properties"), 11)) {
542 if (!processObjectGroupProperties(child, &zpos))
543 return false;
545 else if (!xmlStrncmp(child->name, BAD_CAST("object"), 7)) {
546 if (zpos == -1 || !processObject(child, zpos))
547 return false;
551 return true;
554 bool Area::processObjectGroupProperties(xmlNode* node, int* zpos)
558 <properties>
559 <property name="layer" value="0"/>
560 </properties>
563 xmlNode* child = node->xmlChildrenNode;
564 for (; child != NULL; child = child->next) {
565 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
566 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
567 if (!xmlStrncmp(name, BAD_CAST("layer"), 6)) {
568 int layer = atoi((const char*)value);
569 if (0 < layer || layer >= (int)dim.z) {
570 // XXX we need to know the Area we're loading...
571 Log::err("unknown area",
572 "objectgroup must correspond with layer"
574 return false;
576 *zpos = layer;
579 return true;
582 bool Area::processObject(xmlNode* node, int zpos)
586 <object name="tile2" type="Tile" gid="7" x="64" y="320">
587 <properties>
588 <property name="onEnter" value="speed(0.5)"/>
589 <property name="onLeave" value="undo()"/>
590 <property name="door" value="grassfield.area,1,1,0"/>
591 <property name="flags" value="npc_nowalk"/>
592 </properties>
593 </object>
596 xmlChar* type = xmlGetProp(node, BAD_CAST("type"));
597 if (xmlStrncmp(type, BAD_CAST("Tile"), 5)) {
598 Log::err("unknown area", "object type must be Tile");
599 return false;
602 xmlChar* xStr = xmlGetProp(node, BAD_CAST("x"));
603 xmlChar* yStr = xmlGetProp(node, BAD_CAST("y"));
604 // XXX we ignore the object gid... is that okay?
606 // wouldn't have to access tilesets if we had tiledim ourselves
607 long x = atol((const char*)xStr) / tilesets[0].tiledim.x;
608 long y = atol((const char*)yStr) / tilesets[0].tiledim.y;
609 y = y - 1; // bug in tiled? y is 1 too high
611 // We know which Tile is being talked about now... yay
612 Tile* t = map[zpos][y][x];
614 xmlNode* child = node->xmlChildrenNode; // <properties>
615 child = child->xmlChildrenNode; // <property>
616 for (; child != NULL; child = child->next) {
617 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
618 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
619 if (!xmlStrncmp(name, BAD_CAST("flags"), 6)) {
620 t->flags = splitTileFlags((const char*)value);
622 else if (!xmlStrncmp(name, BAD_CAST("onEnter"), 8)) {
623 // TODO events
625 else if (!xmlStrncmp(name, BAD_CAST("onLeave"), 8)) {
626 // TODO events
628 else if (!xmlStrncmp(name, BAD_CAST("door"), 5)) {
629 t->door = parseDoor((const char*)value);
630 t->flags |= npc_nowalk;
633 return true;
636 unsigned Area::splitTileFlags(const std::string strOfFlags)
638 std::vector<std::string> strs;
639 strs = splitStr(strOfFlags, ",");
641 unsigned flags = 0x0;
642 BOOST_FOREACH(std::string str, strs)
644 // TODO: reimplement comparisons as a hash table
645 if (str == "nowalk") {
646 flags |= nowalk;
649 return flags;
652 Area::Door* Area::parseDoor(const std::string dest)
654 std::vector<std::string> strs;
655 strs = splitStr(dest, ",");
657 // TODO: verify the validity of the input string... it's coming from
658 // user land
659 Door* door = new Door;
660 door->area = strs[0];
661 door->coord.x = atol(strs[1].c_str());
662 door->coord.y = atol(strs[2].c_str());
663 door->coord.z = atol(strs[3].c_str());
664 return door;
667 coord_t Area::getDimensions() const
669 return dim;
672 coord_t Area::getTileDimensions() const
674 return tilesets[0].tiledim;
677 Area::Tile* Area::getTile(coord_t c)
679 return map[c.z][c.y][c.x];