impl'd processObjectGroup
[Tsunagari.git] / src / area.cpp
blob902fbe72c7773f013eb38e0aa8928d49f435bbbf
1 /******************************
2 ** Tsunagari Tile Engine **
3 ** area.cpp **
4 ** Copyright 2011 OmegaSDG **
5 ******************************/
7 #include <boost/shared_ptr.hpp>
8 #include <libxml/parser.h>
9 #include <libxml/tree.h>
11 #include "area.h"
12 #include "common.h"
13 #include "entity.h"
14 #include "log.h"
15 #include "resourcer.h"
16 #include "sprite.h"
18 Area::Area(Resourcer* rc, Entity* player, const std::string descriptor)
19 : rc(rc), player(player), descriptor(descriptor)
21 dim.z = 0;
24 Area::~Area()
28 bool Area::init()
30 if (!processDescriptor()) // Try to load in descriptor.
31 return false;
32 return true;
35 void Area::buttonDown(const Gosu::Button btn)
37 if (btn == Gosu::kbRight)
38 player->moveByTile(coord(1, 0, 0));
39 else if (btn == Gosu::kbLeft)
40 player->moveByTile(coord(-1, 0, 0));
41 else if (btn == Gosu::kbUp)
42 player->moveByTile(coord(0, -1, 0));
43 else if (btn == Gosu::kbDown)
44 player->moveByTile(coord(0, 1, 0));
47 void Area::draw()
49 for (unsigned int layer = 0; layer != map.size(); layer++)
51 grid_t grid = map[layer];
52 for (unsigned int y = 0; y != grid.size(); y++)
54 row_t row = grid[y];
55 for (unsigned int x = 0; x != row.size(); x++)
57 // TODO support animations
58 Tile* tile = row[x];
59 Gosu::Image* img = tile->type->graphics[0];
60 img->draw(x*img->width(), y*img->height(), 0);
64 player->draw();
67 bool Area::needsRedraw() const
69 return player->needsRedraw();
72 bool Area::processDescriptor()
74 xmlDoc* doc = rc->getXMLDoc(descriptor);
75 if (!doc)
76 return false;
78 // use RAII to ensure doc is freed
79 boost::shared_ptr<void> alwaysFreeTheDoc(doc, xmlFreeDoc);
81 // Iterate and process children of <map>
82 xmlNode* root = xmlDocGetRootElement(doc); // <map> element
84 xmlChar* width = xmlGetProp(root, BAD_CAST("width"));
85 xmlChar* height = xmlGetProp(root, BAD_CAST("height"));
86 dim.x = atol((const char*)width);
87 dim.y = atol((const char*)height);
89 xmlNode* child = root->xmlChildrenNode;
90 for (; child != NULL; child = child->next) {
91 if (!xmlStrncmp(child->name, BAD_CAST("properties"), 11)) {
92 if (!processMapProperties(child))
93 return false;
95 else if (!xmlStrncmp(child->name, BAD_CAST("tileset"), 8)) {
96 if (!processTileset(child))
97 return false;
99 else if (!xmlStrncmp(child->name, BAD_CAST("layer"), 6)) {
100 if (!processLayer(child))
101 return false;
103 else if (!xmlStrncmp(child->name, BAD_CAST("objectgroup"), 12)) {
104 if (!processObjectGroup(child))
105 return false;
109 return true;
112 bool Area::processMapProperties(xmlNode* node)
116 <properties>
117 <property name="areaspec" value="1"/>
118 <property name="author" value="Michael D. Reiley"/>
119 <property name="name" value="Baby's First Area"/>
120 <property name="music_loop" value="true"/>
121 <property name="music_main" value="wind.music"/>
122 <property name="onLoad" value="babysfirst_init()"/>
123 <property name="scripts" value="areainits.event,test.event"/>
124 </properties>
127 xmlNode* child = node->xmlChildrenNode;
128 for (; child != NULL; child = child->next) {
129 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
130 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
131 if (!xmlStrncmp(name, BAD_CAST("author"), 7))
132 author = (const char*)value;
133 else if (!xmlStrncmp(name, BAD_CAST("name"), 5))
134 this->name = (const char*)value;
135 else if (!xmlStrncmp(name, BAD_CAST("music_loop"), 11))
136 main.loop = parseBool((const char*)value);
137 else if (!xmlStrncmp(name, BAD_CAST("music_main"), 11))
138 main.filename = (const char*)value;
139 else if (!xmlStrncmp(name, BAD_CAST("onLoad"), 7))
140 onLoadEvents = (const char*)value;
141 else if (!xmlStrncmp(name, BAD_CAST("scripts"), 8))
142 scripts = (const char*)value; // TODO split(), load
144 return true;
147 bool Area::processTileset(xmlNode* node)
151 <tileset firstgid="1" name="tiles.sheet" tilewidth="64" tileheight="64">
152 <image source="tiles.sheet" width="256" height="256"/>
153 <tile id="14">
155 </tile>
156 </tileset>
158 Tileset ts;
159 xmlChar* width = xmlGetProp(node, BAD_CAST("tilewidth"));
160 xmlChar* height = xmlGetProp(node, BAD_CAST("tileheight"));
161 ts.tiledim.x = atol((const char*)width);
162 ts.tiledim.y = atol((const char*)height);
164 xmlNode* child = node->xmlChildrenNode;
165 for (; child != NULL; child = child->next) {
166 if (!xmlStrncmp(child->name, BAD_CAST("tile"), 5)) {
167 xmlChar* idstr = xmlGetProp(child, BAD_CAST("id"));
168 unsigned id = atol((const char*)idstr);
170 // Undeclared TileTypes have default properties.
171 while (ts.defaults.size() != id) {
172 TileType tt = defaultTileType(ts.source,
173 ts.tiledim, ts.defaults.size());
174 ts.defaults.push_back(tt);
177 // Handle explicit TileType
178 if (!processTileType(child, ts))
179 return false;
181 else if (!xmlStrncmp(child->name, BAD_CAST("image"), 6)) {
182 xmlChar* source = xmlGetProp(child, BAD_CAST("source"));
183 ts.source = rc->getBitmap((const char*)source);
187 // Generate default tile types in range (m,n] where m is the last
188 // explicitly declared type and n is the number we require.
189 unsigned srcsz = ts.source.width() * ts.source.height();
190 unsigned tilesz = ts.tiledim.x * ts.tiledim.y;
191 while (ts.defaults.size() != srcsz / tilesz) {
192 TileType tt = defaultTileType(ts.source,
193 ts.tiledim, ts.defaults.size());
194 ts.defaults.push_back(tt);
197 tilesets.push_back(ts);
198 return true;
201 Area::TileType Area::defaultTileType(const Gosu::Bitmap source, coord_t tiledim,
202 int id)
204 int x = (tiledim.x * id) % source.width();
205 int y = (tiledim.y * id) / source.height() * tiledim.y; // ???
207 TileType tt;
208 Gosu::Image* img = rc->bitmapSection(source, x, y,
209 tiledim.x, tiledim.y, true);
210 tt.graphics.push_back(img);
211 tt.animated = false;
212 tt.ani_speed = 0.0;
213 return tt;
216 bool Area::processTileType(xmlNode* node, Tileset& ts)
220 <tile id="8">
221 <properties>
222 <property name="flags" value="nowalk"/>
223 <property name="onEnter" value="skid();speed(2)"/>
224 <property name="onLeave" value="undo()"/>
225 </properties>
226 </tile>
227 <tile id="14">
228 <properties>
229 <property name="animated" value="1"/>
230 <property name="size" value="2"/>
231 <property name="speed" value="2"/>
232 </properties>
233 </tile>
236 // Initialize a default TileType, we'll build on that.
237 TileType tt = defaultTileType(ts.source,
238 ts.tiledim, ts.defaults.size());
240 xmlChar* idstr = xmlGetProp(node, BAD_CAST("id"));
241 unsigned id = atol((const char*)idstr);
242 if (id != ts.defaults.size()) {
243 // XXX we need to know the Area we're loading...
244 Log::err("unknown area", std::string("expected TileType id ") +
245 itostr(ts.defaults.size()) + ", but got " + itostr(id));
246 return false;
249 xmlNode* child = node->xmlChildrenNode; // <properties>
250 child = node->xmlChildrenNode; // <property>
251 for (; child != NULL; child = child->next) {
252 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
253 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
254 if (!xmlStrncmp(child->name, BAD_CAST("flags"), 6)) {
255 // TODO flags
257 else if (!xmlStrncmp(name, BAD_CAST("onEnter"), 8)) {
258 // TODO events
260 else if (!xmlStrncmp(name, BAD_CAST("onLeave"), 8)) {
261 // TODO events
263 else if (!xmlStrncmp(name, BAD_CAST("animated"), 9)) {
264 tt.animated = parseBool((const char*)value);
266 else if (!xmlStrncmp(name, BAD_CAST("size"), 5)) {
267 // TODO animation
269 else if (!xmlStrncmp(name, BAD_CAST("speed"), 6)) {
270 tt.ani_speed = atol((const char*)value);
274 ts.defaults.push_back(tt);
275 return true;
278 bool Area::processLayer(xmlNode* node)
282 <layer name="Tiles0" width="5" height="5">
283 <properties>
285 </properties>
286 <data>
287 <tile gid="9"/>
288 <tile gid="9"/>
289 <tile gid="9"/>
291 <tile gid="3"/>
292 <tile gid="9"/>
293 <tile gid="9"/>
294 </data>
295 </layer>
298 xmlChar* width = xmlGetProp(node, BAD_CAST("width"));
299 xmlChar* height = xmlGetProp(node, BAD_CAST("height"));
300 unsigned x = atol((const char*)width);
301 unsigned y = atol((const char*)height);
303 if (dim.x != x || dim.y != y) {
304 // XXX we need to know the Area we're loading...
305 Log::err("unknown area", "layer x,y size != map x,y size");
306 return false;
309 xmlNode* child = node->xmlChildrenNode;
310 for (; child != NULL; child = child->next) {
311 if (!xmlStrncmp(child->name, BAD_CAST("properties"), 11)) {
312 if (!processLayerProperties(child))
313 return false;
315 else if (!xmlStrncmp(child->name, BAD_CAST("data"), 5)) {
316 if (!processLayerData(child))
317 return false;
320 return true;
323 bool Area::processLayerProperties(xmlNode* node)
327 <properties>
328 <property name="layer" value="0"/>
329 </properties>
332 xmlNode* child = node->xmlChildrenNode;
333 for (; child != NULL; child = child->next) {
334 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
335 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
336 if (!xmlStrncmp(name, BAD_CAST("layer"), 6)) {
337 unsigned depth = atol((const char*)value);
338 if (depth != dim.z) {
339 Log::err("unknown area", "invalid layer depth");
340 return false;
345 return true;
348 bool Area::processLayerData(xmlNode* node)
352 <data>
353 <tile gid="9"/>
354 <tile gid="9"/>
355 <tile gid="9"/>
357 <tile gid="3"/>
358 <tile gid="9"/>
359 <tile gid="9"/>
360 </data>
363 row_t row;
364 grid_t grid;
366 xmlNode* child = node->xmlChildrenNode;
367 for (int i = 1; child != NULL; i++, child = child->next) {
368 if (!xmlStrncmp(child->name, BAD_CAST("tile"), 5)) {
369 xmlChar* gidStr = xmlGetProp(child, BAD_CAST("gid"));
370 unsigned gid = atol((const char*)gidStr);
371 Tile* t = new Tile;
372 t->type = &tilesets[0].defaults[gid]; // XXX can only access first tileset
373 row.push_back(t);
374 if (i % dim.x == 0) {
375 grid.push_back(row);
376 row.clear();
381 map.push_back(grid);
382 dim.z++;
383 return true;
386 bool Area::processObjectGroup(xmlNode* node)
390 <objectgroup name="Prop0" width="5" height="5">
391 <properties>
392 <property name="layer" value="0"/>
393 </properties>
394 <object name="tile2" type="Tile" gid="7" x="64" y="320">
395 <properties>
396 <property name="onEnter" value="speed(0.5)"/>
397 <property name="onLeave" value="undo()"/>
398 <property name="door" value="grassfield.area,1,1,0"/>
399 <property name="flags" value="npc_nowalk"/>
400 </properties>
401 </object>
402 </objectgroup>
405 xmlChar* width = xmlGetProp(node, BAD_CAST("width"));
406 xmlChar* height = xmlGetProp(node, BAD_CAST("height"));
407 unsigned x = atol((const char*)width);
408 unsigned y = atol((const char*)height);
410 unsigned zpos = -1;
412 if (dim.x != x || dim.y != y) {
413 // XXX we need to know the Area we're loading...
414 Log::err("unknown area", "objectgroup x,y size != map x,y size");
415 return false;
418 xmlNode* child = node->xmlChildrenNode;
419 for (; child != NULL; child = child->next) {
420 if (!xmlStrncmp(child->name, BAD_CAST("properties"), 11)) {
421 if (!processObjectGroupProperties(child, &zpos))
422 return false;
424 else if (!xmlStrncmp(child->name, BAD_CAST("object"), 7)) {
425 if (zpos == (unsigned)-1 || !processObject(child, zpos))
426 return false;
430 return true;
433 bool Area::processObjectGroupProperties(xmlNode* node, unsigned* zpos)
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 layer = atol((const char*)value);
448 if (0 < layer || layer >= (int)dim.z) {
449 // XXX we need to know the Area we're loading...
450 Log::err("unknown area",
451 "objectgroup must correspond with layer"
453 return false;
455 *zpos = layer;
458 return true;
461 bool Area::processObject(xmlNode* node, unsigned zpos)
465 <object name="tile2" type="Tile" gid="7" x="64" y="320">
466 <properties>
467 <property name="onEnter" value="speed(0.5)"/>
468 <property name="onLeave" value="undo()"/>
469 <property name="door" value="grassfield.area,1,1,0"/>
470 <property name="flags" value="npc_nowalk"/>
471 </properties>
472 </object>
475 xmlChar* type = xmlGetProp(node, BAD_CAST("type"));
476 if (xmlStrncmp(type, BAD_CAST("Tile"), 5)) {
477 Log::err("unknown area", "object type must be Tile");
478 return false;
481 xmlChar* xStr = xmlGetProp(node, BAD_CAST("x"));
482 xmlChar* yStr = xmlGetProp(node, BAD_CAST("y"));
483 // XXX we ignore the object gid... is that okay?
485 // wouldn't have to access tilesets if we had tiledim ourselves
486 unsigned x = atol((const char*)xStr) / tilesets[0].tiledim.x;
487 unsigned y = atol((const char*)yStr) / tilesets[0].tiledim.y;
488 y = y - 1; // bug in tiled? y is 1 too high
490 // We know which Tile is being talked about now... yay
491 Tile* t = map[zpos][y][x];
493 xmlNode* child = node->xmlChildrenNode; // <properties>
494 child = node->xmlChildrenNode; // <property>
495 for (; child != NULL; child = child->next) {
496 xmlChar* name = xmlGetProp(child, BAD_CAST("name"));
497 xmlChar* value = xmlGetProp(child, BAD_CAST("value"));
498 if (!xmlStrncmp(child->name, BAD_CAST("flags"), 6)) {
499 // TODO flags
501 else if (!xmlStrncmp(name, BAD_CAST("onEnter"), 8)) {
502 // TODO events
504 else if (!xmlStrncmp(name, BAD_CAST("onLeave"), 8)) {
505 // TODO events
507 else if (!xmlStrncmp(name, BAD_CAST("door"), 5)) {
508 // TODO doors
511 return true;
514 coord_t Area::getDimensions() const
516 return dim;
519 Area::Tile* Area::getTile(coord_t c)
521 return map[c.z][c.y][c.x];