'nowalk' flag implemented
[Tsunagari.git] / src / area.cpp
blob992be52cd2b15532b187610a5069314fd0cf9078
1 /******************************
2 ** Tsunagari Tile Engine **
3 ** area.cpp **
4 ** Copyright 2011 OmegaSDG **
5 ******************************/
7 #include <boost/algorithm/string.hpp>
8 #include <boost/foreach.hpp>
9 #include <boost/shared_ptr.hpp>
11 #include "area.h"
12 #include "common.h"
13 #include "entity.h"
14 #include "log.h"
15 #include "resourcer.h"
16 #include "sprite.h"
17 #include "window.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, Entity* player, const std::string& descriptor)
25 : rc(rc), 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 if (btn == Gosu::kbRight)
44 player->moveByTile(coord(1, 0, 0));
45 else if (btn == Gosu::kbLeft)
46 player->moveByTile(coord(-1, 0, 0));
47 else if (btn == Gosu::kbUp)
48 player->moveByTile(coord(0, -1, 0));
49 else if (btn == Gosu::kbDown)
50 player->moveByTile(coord(0, 1, 0));
53 void Area::draw()
55 GameWindow* window = GameWindow::getWindow();
56 Gosu::Graphics* graphics = &window->graphics();
57 Gosu::Transform trans = translateCoords();
58 graphics->pushTransform(trans);
60 for (unsigned int layer = 0; layer != map.size(); layer++)
62 grid_t grid = map[layer];
63 for (unsigned int y = 0; y != grid.size(); y++)
65 row_t row = grid[y];
66 for (unsigned int x = 0; x != row.size(); x++)
68 // TODO support animations
69 Tile* tile = row[x];
70 Gosu::Image* img = tile->type->graphics[0];
71 img->draw(x*img->width(), y*img->height(), 0);
75 player->draw();
77 graphics->popTransform();
80 static double bound(double low, double x, double high)
82 if (low > x)
83 x = low;
84 if (x > high)
85 x = high;
86 return x;
89 static double center(double w, double g, double p)
91 return w>g ? (w-g)/2.0 : bound(w-g, w/2.0-p, 0);
94 Gosu::Transform Area::translateCoords()
96 GameWindow* window = GameWindow::getWindow();
97 Gosu::Graphics* graphics = &window->graphics();
99 // FIXME: horrible
100 double tileSize = map[0][0][0]->type->graphics[0]->width();
101 double windowWidth = graphics->width() / tileSize;
102 double windowHeight = graphics->height() / tileSize;
103 double gridWidth = dim.x;
104 double gridHeight = dim.y;
105 double playerX = player->getCoordsByPixel().x / tileSize + 0.5;
106 double playerY = player->getCoordsByPixel().y / tileSize + 0.5;
108 coord_t c;
109 c.x = center(windowWidth, gridWidth, playerX) * tileSize;
110 c.y = center(windowHeight, gridHeight, playerY) * tileSize;
112 Gosu::Transform trans = Gosu::translate(c.x, c.y);
113 return trans;
116 bool Area::needsRedraw() const
118 return player->needsRedraw();
121 bool Area::processDescriptor()
123 xmlDoc* doc = rc->getXMLDoc(descriptor);
124 if (!doc)
125 return false;
127 // use RAII to ensure doc is freed
128 boost::shared_ptr<void> alwaysFreeTheDoc(doc, xmlFreeDoc);
130 // Iterate and process children of <map>
131 xmlNode* root = xmlDocGetRootElement(doc); // <map> element
133 xmlChar* width = xmlGetProp(root, BAD_CAST("width"));
134 xmlChar* height = xmlGetProp(root, BAD_CAST("height"));
135 dim.x = atol((const char*)width);
136 dim.y = atol((const char*)height);
138 xmlNode* child = root->xmlChildrenNode;
139 for (; child != NULL; child = child->next) {
140 if (!xmlStrncmp(child->name, BAD_CAST("properties"), 11)) {
141 if (!processMapProperties(child))
142 return false;
144 else if (!xmlStrncmp(child->name, BAD_CAST("tileset"), 8)) {
145 if (!processTileset(child))
146 return false;
148 else if (!xmlStrncmp(child->name, BAD_CAST("layer"), 6)) {
149 if (!processLayer(child))
150 return false;
152 else if (!xmlStrncmp(child->name, BAD_CAST("objectgroup"), 12)) {
153 if (!processObjectGroup(child))
154 return false;
158 return true;
161 bool Area::processMapProperties(xmlNode* node)
165 <properties>
166 <property name="areaspec" value="1"/>
167 <property name="author" value="Michael D. Reiley"/>
168 <property name="name" value="Baby's First Area"/>
169 <property name="music_loop" value="true"/>
170 <property name="music_main" value="wind.music"/>
171 <property name="onLoad" value="babysfirst_init()"/>
172 <property name="scripts" value="areainits.event,test.event"/>
173 </properties>
176 xmlNode* child = node->xmlChildrenNode;
177 for (; child != NULL; child = child->next) {
178 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
179 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
180 if (!xmlStrncmp(name, BAD_CAST("author"), 7))
181 author = (const char*)value;
182 else if (!xmlStrncmp(name, BAD_CAST("name"), 5))
183 this->name = (const char*)value;
184 else if (!xmlStrncmp(name, BAD_CAST("music_loop"), 11))
185 main.loop = parseBool((const char*)value);
186 else if (!xmlStrncmp(name, BAD_CAST("music_main"), 11))
187 main.filename = (const char*)value;
188 else if (!xmlStrncmp(name, BAD_CAST("onLoad"), 7))
189 onLoadEvents = (const char*)value;
190 else if (!xmlStrncmp(name, BAD_CAST("scripts"), 8))
191 scripts = (const char*)value; // TODO split(), load
193 return true;
196 bool Area::processTileset(xmlNode* node)
200 <tileset firstgid="1" name="tiles.sheet" tilewidth="64" tileheight="64">
201 <image source="tiles.sheet" width="256" height="256"/>
202 <tile id="14">
204 </tile>
205 </tileset>
207 Tileset ts;
208 xmlChar* width = xmlGetProp(node, BAD_CAST("tilewidth"));
209 xmlChar* height = xmlGetProp(node, BAD_CAST("tileheight"));
210 ts.tiledim.x = atol((const char*)width);
211 ts.tiledim.y = atol((const char*)height);
213 xmlNode* child = node->xmlChildrenNode;
214 for (; child != NULL; child = child->next) {
215 if (!xmlStrncmp(child->name, BAD_CAST("tile"), 5)) {
216 xmlChar* idstr = xmlGetProp(child, BAD_CAST("id"));
217 unsigned id = atol((const char*)idstr);
219 // Undeclared TileTypes have default properties.
220 while (ts.defaults.size() != id) {
221 TileType tt = defaultTileType(ts.source,
222 ts.tiledim, ts.defaults.size());
223 ts.defaults.push_back(tt);
226 // Handle explicit TileType
227 if (!processTileType(child, ts))
228 return false;
230 else if (!xmlStrncmp(child->name, BAD_CAST("image"), 6)) {
231 xmlChar* source = xmlGetProp(child, BAD_CAST("source"));
232 ts.source = rc->getBitmap((const char*)source);
236 // Generate default tile types in range (m,n] where m is the last
237 // explicitly declared type and n is the number we require.
238 unsigned srcsz = ts.source.width() * ts.source.height();
239 unsigned tilesz = ts.tiledim.x * ts.tiledim.y;
240 while (ts.defaults.size() != srcsz / tilesz) {
241 TileType tt = defaultTileType(ts.source,
242 ts.tiledim, ts.defaults.size());
243 ts.defaults.push_back(tt);
246 tilesets.push_back(ts);
247 return true;
250 Area::TileType Area::defaultTileType(const Gosu::Bitmap source, coord_t tiledim,
251 int id)
253 int x = (tiledim.x * id) % source.width();
254 int y = (tiledim.y * id) / source.height() * tiledim.y; // ???
256 TileType tt;
257 Gosu::Image* img = rc->bitmapSection(source, x, y,
258 tiledim.x, tiledim.y, true);
259 tt.graphics.push_back(img);
260 tt.animated = false;
261 tt.ani_speed = 0.0;
262 tt.flags = 0x0;
263 return tt;
266 bool Area::processTileType(xmlNode* node, Tileset& ts)
270 <tile id="8">
271 <properties>
272 <property name="flags" value="nowalk"/>
273 <property name="onEnter" value="skid();speed(2)"/>
274 <property name="onLeave" value="undo()"/>
275 </properties>
276 </tile>
277 <tile id="14">
278 <properties>
279 <property name="animated" value="1"/>
280 <property name="size" value="2"/>
281 <property name="speed" value="2"/>
282 </properties>
283 </tile>
286 // Initialize a default TileType, we'll build on that.
287 TileType tt = defaultTileType(ts.source,
288 ts.tiledim, ts.defaults.size());
290 xmlChar* idstr = xmlGetProp(node, BAD_CAST("id"));
291 unsigned id = atol((const char*)idstr);
292 if (id != ts.defaults.size()) {
293 // XXX we need to know the Area we're loading...
294 Log::err("unknown area", std::string("expected TileType id ") +
295 itostr(ts.defaults.size()) + ", but got " + itostr(id));
296 return false;
299 xmlNode* child = node->xmlChildrenNode; // <properties>
300 child = child->xmlChildrenNode; // <property>
301 for (; child != NULL; child = child->next) {
302 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
303 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
304 if (!xmlStrncmp(name, BAD_CAST("flags"), 6)) {
305 tt.flags = splitTileFlags((const char*)value);
307 else if (!xmlStrncmp(name, BAD_CAST("onEnter"), 8)) {
308 // TODO events
310 else if (!xmlStrncmp(name, BAD_CAST("onLeave"), 8)) {
311 // TODO events
313 else if (!xmlStrncmp(name, BAD_CAST("animated"), 9)) {
314 tt.animated = parseBool((const char*)value);
316 else if (!xmlStrncmp(name, BAD_CAST("size"), 5)) {
317 // TODO animation
319 else if (!xmlStrncmp(name, BAD_CAST("speed"), 6)) {
320 tt.ani_speed = atol((const char*)value);
324 ts.defaults.push_back(tt);
325 return true;
328 bool Area::processLayer(xmlNode* node)
332 <layer name="Tiles0" width="5" height="5">
333 <properties>
335 </properties>
336 <data>
337 <tile gid="9"/>
338 <tile gid="9"/>
339 <tile gid="9"/>
341 <tile gid="3"/>
342 <tile gid="9"/>
343 <tile gid="9"/>
344 </data>
345 </layer>
348 xmlChar* width = xmlGetProp(node, BAD_CAST("width"));
349 xmlChar* height = xmlGetProp(node, BAD_CAST("height"));
350 int x = atol((const char*)width);
351 int y = atol((const char*)height);
353 if (dim.x != x || dim.y != y) {
354 // XXX we need to know the Area we're loading...
355 Log::err("unknown area", "layer x,y size != map x,y size");
356 return false;
359 xmlNode* child = node->xmlChildrenNode;
360 for (; child != NULL; child = child->next) {
361 if (!xmlStrncmp(child->name, BAD_CAST("properties"), 11)) {
362 if (!processLayerProperties(child))
363 return false;
365 else if (!xmlStrncmp(child->name, BAD_CAST("data"), 5)) {
366 if (!processLayerData(child))
367 return false;
370 return true;
373 bool Area::processLayerProperties(xmlNode* node)
377 <properties>
378 <property name="layer" value="0"/>
379 </properties>
382 xmlNode* child = node->xmlChildrenNode;
383 for (; child != NULL; child = child->next) {
384 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
385 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
386 if (!xmlStrncmp(name, BAD_CAST("layer"), 6)) {
387 int depth = atol((const char*)value);
388 if (depth != dim.z) {
389 Log::err("unknown area", "invalid layer depth");
390 return false;
395 return true;
398 bool Area::processLayerData(xmlNode* node)
402 <data>
403 <tile gid="9"/>
404 <tile gid="9"/>
405 <tile gid="9"/>
407 <tile gid="3"/>
408 <tile gid="9"/>
409 <tile gid="9"/>
410 </data>
413 row_t row;
414 grid_t grid;
416 xmlNode* child = node->xmlChildrenNode;
417 for (int i = 1; child != NULL; i++, child = child->next) {
418 if (!xmlStrncmp(child->name, BAD_CAST("tile"), 5)) {
419 xmlChar* gidStr = xmlGetProp(child, BAD_CAST("gid"));
420 unsigned gid = atol((const char*)gidStr)-1;
421 Tile* t = new Tile;
422 t->type = &tilesets[0].defaults[gid]; // XXX can only access first tileset
423 t->flags = 0x0;
424 row.push_back(t);
425 if (row.size() % dim.x == 0) {
426 grid.push_back(row);
427 row.clear();
432 map.push_back(grid);
433 dim.z++;
434 return true;
437 bool Area::processObjectGroup(xmlNode* node)
441 <objectgroup name="Prop0" width="5" height="5">
442 <properties>
443 <property name="layer" value="0"/>
444 </properties>
445 <object name="tile2" type="Tile" gid="7" x="64" y="320">
446 <properties>
447 <property name="onEnter" value="speed(0.5)"/>
448 <property name="onLeave" value="undo()"/>
449 <property name="door" value="grassfield.area,1,1,0"/>
450 <property name="flags" value="npc_nowalk"/>
451 </properties>
452 </object>
453 </objectgroup>
456 xmlChar* width = xmlGetProp(node, BAD_CAST("width"));
457 xmlChar* height = xmlGetProp(node, BAD_CAST("height"));
458 int x = atol((const char*)width);
459 int y = atol((const char*)height);
461 int zpos = -1;
463 if (dim.x != x || dim.y != y) {
464 // XXX we need to know the Area we're loading...
465 Log::err("unknown area", "objectgroup x,y size != map x,y size");
466 return false;
469 xmlNode* child = node->xmlChildrenNode;
470 for (; child != NULL; child = child->next) {
471 if (!xmlStrncmp(child->name, BAD_CAST("properties"), 11)) {
472 if (!processObjectGroupProperties(child, &zpos))
473 return false;
475 else if (!xmlStrncmp(child->name, BAD_CAST("object"), 7)) {
476 if (zpos == -1 || !processObject(child, zpos))
477 return false;
481 return true;
484 bool Area::processObjectGroupProperties(xmlNode* node, int* zpos)
488 <properties>
489 <property name="layer" value="0"/>
490 </properties>
493 xmlNode* child = node->xmlChildrenNode;
494 for (; child != NULL; child = child->next) {
495 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
496 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
497 if (!xmlStrncmp(name, BAD_CAST("layer"), 6)) {
498 int layer = atol((const char*)value);
499 if (0 < layer || layer >= (int)dim.z) {
500 // XXX we need to know the Area we're loading...
501 Log::err("unknown area",
502 "objectgroup must correspond with layer"
504 return false;
506 *zpos = layer;
509 return true;
512 bool Area::processObject(xmlNode* node, int zpos)
516 <object name="tile2" type="Tile" gid="7" x="64" y="320">
517 <properties>
518 <property name="onEnter" value="speed(0.5)"/>
519 <property name="onLeave" value="undo()"/>
520 <property name="door" value="grassfield.area,1,1,0"/>
521 <property name="flags" value="npc_nowalk"/>
522 </properties>
523 </object>
526 xmlChar* type = xmlGetProp(node, BAD_CAST("type"));
527 if (xmlStrncmp(type, BAD_CAST("Tile"), 5)) {
528 Log::err("unknown area", "object type must be Tile");
529 return false;
532 xmlChar* xStr = xmlGetProp(node, BAD_CAST("x"));
533 xmlChar* yStr = xmlGetProp(node, BAD_CAST("y"));
534 // XXX we ignore the object gid... is that okay?
536 // wouldn't have to access tilesets if we had tiledim ourselves
537 int x = atol((const char*)xStr) / tilesets[0].tiledim.x;
538 int y = atol((const char*)yStr) / tilesets[0].tiledim.y;
539 y = y - 1; // bug in tiled? y is 1 too high
541 // We know which Tile is being talked about now... yay
542 Tile* t = map[zpos][y][x];
544 xmlNode* child = node->xmlChildrenNode; // <properties>
545 child = node->xmlChildrenNode; // <property>
546 for (; child != NULL; child = child->next) {
547 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
548 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
549 if (!xmlStrncmp(child->name, BAD_CAST("flags"), 6)) {
550 t->flags = splitTileFlags((const char*)value);
552 else if (!xmlStrncmp(name, BAD_CAST("onEnter"), 8)) {
553 // TODO events
555 else if (!xmlStrncmp(name, BAD_CAST("onLeave"), 8)) {
556 // TODO events
558 else if (!xmlStrncmp(name, BAD_CAST("door"), 5)) {
559 // TODO doors
562 return true;
565 unsigned Area::splitTileFlags(const std::string strOfFlags)
567 std::vector<std::string> strs;
568 boost::split(strs, strOfFlags, boost::is_any_of(":"));
570 unsigned flags = 0x0;
571 BOOST_FOREACH(std::string str, strs)
573 // TODO: reimplement comparisons as a hash table
574 if (str == "nowalk") {
575 flags |= nowalk;
578 return flags;
581 coord_t Area::getDimensions() const
583 return dim;
586 Area::Tile* Area::getTile(coord_t c)
588 return map[c.z][c.y][c.x];