1 /******************************
2 ** Tsunagari Tile Engine **
4 ** Copyright 2011 OmegaSDG **
5 ******************************/
7 #include <boost/foreach.hpp>
8 #include <boost/shared_ptr.hpp>
14 #include "resourcer.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
,
27 const std::string
& descriptor
)
28 : rc(rc
), world(world
), player(player
), descriptor(descriptor
)
30 dim
.x
= dim
.y
= dim
.z
= 0;
35 // Delete each Tile. If a Tile has an allocated Door struct, delete
37 BOOST_FOREACH(grid_t grid
, map
) {
38 BOOST_FOREACH(row_t row
, grid
) {
39 BOOST_FOREACH(Tile
* tile
, row
) {
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
) {
52 // TODO: delete TileEvents when we start using them
59 return processDescriptor();
62 void Area::buttonDown(const Gosu::Button btn
)
64 bool attemptingMove
= false;
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;
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.
97 world
->loadArea(dest
->door
->area
, dest
->door
->coord
);
100 player
->moveByTile(posMove
);
107 GameWindow
* window
= GameWindow::getWindow();
108 Gosu::Graphics
* graphics
= &window
->graphics();
109 Gosu::Transform trans
= translateCoords();
110 graphics
->pushTransform(trans
);
112 for (unsigned int layer
= 0; layer
!= map
.size(); layer
++)
114 grid_t grid
= map
[layer
];
115 for (unsigned int y
= 0; y
!= grid
.size(); y
++)
118 for (unsigned int x
= 0; x
!= row
.size(); x
++)
120 // TODO support animations
122 Gosu::Image
* img
= tile
->type
->graphics
[0];
123 img
->draw(x
*img
->width(), y
*img
->height(), 0);
129 graphics
->popTransform();
132 //! Returns the number closest to x within the range [low, high].
134 \param low Lowest possible return.
135 \param x Value to be bounded.
136 \param high Highest possible return.
137 \return A number close to x.
138 \sa center() and translateCoords()
140 static double bound(double low
, double x
, double high
)
149 static double center(double w
, double g
, double p
)
151 return w
>g
? (w
-g
)/2.0 : bound(w
-g
, w
/2.0-p
, 0);
154 Gosu::Transform
Area::translateCoords()
156 GameWindow
* window
= GameWindow::getWindow();
157 Gosu::Graphics
* graphics
= &window
->graphics();
159 double tileWidth
= tilesets
[0].tiledim
.x
;
160 double tileHeight
= tilesets
[0].tiledim
.y
;
161 double windowWidth
= graphics
->width() / tileWidth
;
162 double windowHeight
= graphics
->height() / tileHeight
;
163 double gridWidth
= dim
.x
;
164 double gridHeight
= dim
.y
;
165 double playerX
= player
->getCoordsByPixel().x
/ tileWidth
+ 0.5;
166 double playerY
= player
->getCoordsByPixel().y
/ tileHeight
+ 0.5;
169 c
.x
= center(windowWidth
, gridWidth
, playerX
) * tileWidth
;
170 c
.y
= center(windowHeight
, gridHeight
, playerY
) * tileHeight
;
172 Gosu::Transform trans
= Gosu::translate(c
.x
, c
.y
);
176 bool Area::needsRedraw() const
178 return player
->needsRedraw();
181 bool Area::processDescriptor()
183 xmlDoc
* doc
= rc
->getXMLDoc(descriptor
);
187 // use RAII to ensure doc is freed
188 boost::shared_ptr
<void> alwaysFreeTheDoc(doc
, xmlFreeDoc
);
190 // Iterate and process children of <map>
191 xmlNode
* root
= xmlDocGetRootElement(doc
); // <map> element
193 xmlChar
* width
= xmlGetProp(root
, BAD_CAST("width"));
194 xmlChar
* height
= xmlGetProp(root
, BAD_CAST("height"));
195 dim
.x
= atol((const char*)width
);
196 dim
.y
= atol((const char*)height
);
198 xmlNode
* child
= root
->xmlChildrenNode
;
199 for (; child
!= NULL
; child
= child
->next
) {
200 if (!xmlStrncmp(child
->name
, BAD_CAST("properties"), 11)) {
201 if (!processMapProperties(child
))
204 else if (!xmlStrncmp(child
->name
, BAD_CAST("tileset"), 8)) {
205 if (!processTileset(child
))
208 else if (!xmlStrncmp(child
->name
, BAD_CAST("layer"), 6)) {
209 if (!processLayer(child
))
212 else if (!xmlStrncmp(child
->name
, BAD_CAST("objectgroup"), 12)) {
213 if (!processObjectGroup(child
))
221 bool Area::processMapProperties(xmlNode
* node
)
226 <property name="areaspec" value="1"/>
227 <property name="author" value="Michael D. Reiley"/>
228 <property name="name" value="Baby's First Area"/>
229 <property name="music_loop" value="true"/>
230 <property name="music_main" value="wind.music"/>
231 <property name="onLoad" value="babysfirst_init()"/>
232 <property name="scripts" value="areainits.event,test.event"/>
236 xmlNode
* child
= node
->xmlChildrenNode
;
237 for (; child
!= NULL
; child
= child
->next
) {
238 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
239 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
240 if (!xmlStrncmp(name
, BAD_CAST("author"), 7))
241 author
= (const char*)value
;
242 else if (!xmlStrncmp(name
, BAD_CAST("name"), 5))
243 this->name
= (const char*)value
;
244 else if (!xmlStrncmp(name
, BAD_CAST("music_loop"), 11))
245 main
.loop
= parseBool((const char*)value
);
246 else if (!xmlStrncmp(name
, BAD_CAST("music_main"), 11))
247 main
.filename
= (const char*)value
;
248 else if (!xmlStrncmp(name
, BAD_CAST("onLoad"), 7))
249 onLoadEvents
= (const char*)value
;
250 else if (!xmlStrncmp(name
, BAD_CAST("scripts"), 8))
251 scripts
= (const char*)value
; // TODO split(), load
256 bool Area::processTileset(xmlNode
* node
)
260 <tileset firstgid="1" name="tiles.sheet" tilewidth="64" tileheight="64">
261 <image source="tiles.sheet" width="256" height="256"/>
268 xmlChar
* width
= xmlGetProp(node
, BAD_CAST("tilewidth"));
269 xmlChar
* height
= xmlGetProp(node
, BAD_CAST("tileheight"));
270 ts
.tiledim
.x
= atol((const char*)width
);
271 ts
.tiledim
.y
= atol((const char*)height
);
273 xmlNode
* child
= node
->xmlChildrenNode
;
274 for (; child
!= NULL
; child
= child
->next
) {
275 if (!xmlStrncmp(child
->name
, BAD_CAST("tile"), 5)) {
276 xmlChar
* idstr
= xmlGetProp(child
, BAD_CAST("id"));
277 unsigned id
= atol((const char*)idstr
);
279 // Undeclared TileTypes have default properties.
280 while (ts
.defaults
.size() != id
) {
281 TileType tt
= defaultTileType(ts
.source
,
282 ts
.tiledim
, ts
.defaults
.size());
283 ts
.defaults
.push_back(tt
);
286 // Handle explicit TileType
287 if (!processTileType(child
, ts
))
290 else if (!xmlStrncmp(child
->name
, BAD_CAST("image"), 6)) {
291 xmlChar
* source
= xmlGetProp(child
, BAD_CAST("source"));
292 ts
.source
= rc
->getBitmap((const char*)source
);
296 // Generate default tile types in range (m,n] where m is the last
297 // explicitly declared type and n is the number we require.
298 unsigned srcsz
= ts
.source
.width() * ts
.source
.height();
299 unsigned tilesz
= ts
.tiledim
.x
* ts
.tiledim
.y
;
300 while (ts
.defaults
.size() != srcsz
/ tilesz
) {
301 TileType tt
= defaultTileType(ts
.source
,
302 ts
.tiledim
, ts
.defaults
.size());
303 ts
.defaults
.push_back(tt
);
306 tilesets
.push_back(ts
);
310 Area::TileType
Area::defaultTileType(const Gosu::Bitmap source
, coord_t tiledim
,
313 int x
= (tiledim
.x
* id
) % source
.width();
314 int y
= (tiledim
.y
* id
) / source
.height() * tiledim
.y
; // ???
317 Gosu::Image
* img
= rc
->bitmapSection(source
, x
, y
,
318 tiledim
.x
, tiledim
.y
, true);
319 tt
.graphics
.push_back(img
);
326 bool Area::processTileType(xmlNode
* node
, Tileset
& ts
)
332 <property name="flags" value="nowalk"/>
333 <property name="onEnter" value="skid();speed(2)"/>
334 <property name="onLeave" value="undo()"/>
339 <property name="animated" value="1"/>
340 <property name="size" value="2"/>
341 <property name="speed" value="2"/>
346 // Initialize a default TileType, we'll build on that.
347 TileType tt
= defaultTileType(ts
.source
,
348 ts
.tiledim
, ts
.defaults
.size());
350 xmlChar
* idstr
= xmlGetProp(node
, BAD_CAST("id"));
351 unsigned id
= atol((const char*)idstr
);
352 if (id
!= ts
.defaults
.size()) {
353 // XXX we need to know the Area we're loading...
354 Log::err("unknown area", std::string("expected TileType id ") +
355 itostr(ts
.defaults
.size()) + ", but got " + itostr(id
));
359 xmlNode
* child
= node
->xmlChildrenNode
; // <properties>
360 child
= child
->xmlChildrenNode
; // <property>
361 for (; child
!= NULL
; child
= child
->next
) {
362 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
363 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
364 if (!xmlStrncmp(name
, BAD_CAST("flags"), 6)) {
365 tt
.flags
= splitTileFlags((const char*)value
);
367 else if (!xmlStrncmp(name
, BAD_CAST("onEnter"), 8)) {
370 else if (!xmlStrncmp(name
, BAD_CAST("onLeave"), 8)) {
373 else if (!xmlStrncmp(name
, BAD_CAST("animated"), 9)) {
374 tt
.animated
= parseBool((const char*)value
);
376 else if (!xmlStrncmp(name
, BAD_CAST("size"), 5)) {
379 else if (!xmlStrncmp(name
, BAD_CAST("speed"), 6)) {
380 tt
.ani_speed
= atol((const char*)value
);
384 ts
.defaults
.push_back(tt
);
388 bool Area::processLayer(xmlNode
* node
)
392 <layer name="Tiles0" width="5" height="5">
408 xmlChar
* width
= xmlGetProp(node
, BAD_CAST("width"));
409 xmlChar
* height
= xmlGetProp(node
, BAD_CAST("height"));
410 int x
= atol((const char*)width
);
411 int y
= atol((const char*)height
);
413 if (dim
.x
!= x
|| dim
.y
!= y
) {
414 // XXX we need to know the Area we're loading...
415 Log::err("unknown area", "layer x,y size != map x,y size");
419 xmlNode
* child
= node
->xmlChildrenNode
;
420 for (; child
!= NULL
; child
= child
->next
) {
421 if (!xmlStrncmp(child
->name
, BAD_CAST("properties"), 11)) {
422 if (!processLayerProperties(child
))
425 else if (!xmlStrncmp(child
->name
, BAD_CAST("data"), 5)) {
426 if (!processLayerData(child
))
433 bool Area::processLayerProperties(xmlNode
* node
)
438 <property name="layer" value="0"/>
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 depth
= atol((const char*)value
);
448 if (depth
!= dim
.z
) {
449 Log::err("unknown area", "invalid layer depth");
458 bool Area::processLayerData(xmlNode
* node
)
476 xmlNode
* child
= node
->xmlChildrenNode
;
477 for (int i
= 1; child
!= NULL
; i
++, child
= child
->next
) {
478 if (!xmlStrncmp(child
->name
, BAD_CAST("tile"), 5)) {
479 xmlChar
* gidStr
= xmlGetProp(child
, BAD_CAST("gid"));
480 unsigned gid
= atol((const char*)gidStr
)-1;
482 t
->type
= &tilesets
[0].defaults
[gid
]; // XXX can only access first tileset
486 if (row
.size() % dim
.x
== 0) {
498 bool Area::processObjectGroup(xmlNode
* node
)
502 <objectgroup name="Prop0" width="5" height="5">
504 <property name="layer" value="0"/>
506 <object name="tile2" type="Tile" gid="7" x="64" y="320">
508 <property name="onEnter" value="speed(0.5)"/>
509 <property name="onLeave" value="undo()"/>
510 <property name="door" value="grassfield.area,1,1,0"/>
511 <property name="flags" value="npc_nowalk"/>
517 xmlChar
* width
= xmlGetProp(node
, BAD_CAST("width"));
518 xmlChar
* height
= xmlGetProp(node
, BAD_CAST("height"));
519 int x
= atol((const char*)width
);
520 int y
= atol((const char*)height
);
524 if (dim
.x
!= x
|| dim
.y
!= y
) {
525 // XXX we need to know the Area we're loading...
526 Log::err("unknown area", "objectgroup x,y size != map x,y size");
530 xmlNode
* child
= node
->xmlChildrenNode
;
531 for (; child
!= NULL
; child
= child
->next
) {
532 if (!xmlStrncmp(child
->name
, BAD_CAST("properties"), 11)) {
533 if (!processObjectGroupProperties(child
, &zpos
))
536 else if (!xmlStrncmp(child
->name
, BAD_CAST("object"), 7)) {
537 if (zpos
== -1 || !processObject(child
, zpos
))
545 bool Area::processObjectGroupProperties(xmlNode
* node
, int* zpos
)
550 <property name="layer" value="0"/>
554 xmlNode
* child
= node
->xmlChildrenNode
;
555 for (; child
!= NULL
; child
= child
->next
) {
556 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
557 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
558 if (!xmlStrncmp(name
, BAD_CAST("layer"), 6)) {
559 int layer
= atol((const char*)value
);
560 if (0 < layer
|| layer
>= (int)dim
.z
) {
561 // XXX we need to know the Area we're loading...
562 Log::err("unknown area",
563 "objectgroup must correspond with layer"
573 bool Area::processObject(xmlNode
* node
, int zpos
)
577 <object name="tile2" type="Tile" gid="7" x="64" y="320">
579 <property name="onEnter" value="speed(0.5)"/>
580 <property name="onLeave" value="undo()"/>
581 <property name="door" value="grassfield.area,1,1,0"/>
582 <property name="flags" value="npc_nowalk"/>
587 xmlChar
* type
= xmlGetProp(node
, BAD_CAST("type"));
588 if (xmlStrncmp(type
, BAD_CAST("Tile"), 5)) {
589 Log::err("unknown area", "object type must be Tile");
593 xmlChar
* xStr
= xmlGetProp(node
, BAD_CAST("x"));
594 xmlChar
* yStr
= xmlGetProp(node
, BAD_CAST("y"));
595 // XXX we ignore the object gid... is that okay?
597 // wouldn't have to access tilesets if we had tiledim ourselves
598 int x
= atol((const char*)xStr
) / tilesets
[0].tiledim
.x
;
599 int y
= atol((const char*)yStr
) / tilesets
[0].tiledim
.y
;
600 y
= y
- 1; // bug in tiled? y is 1 too high
602 // We know which Tile is being talked about now... yay
603 Tile
* t
= map
[zpos
][y
][x
];
605 xmlNode
* child
= node
->xmlChildrenNode
; // <properties>
606 child
= child
->xmlChildrenNode
; // <property>
607 for (; child
!= NULL
; child
= child
->next
) {
608 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
609 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
610 if (!xmlStrncmp(name
, BAD_CAST("flags"), 6)) {
611 t
->flags
= splitTileFlags((const char*)value
);
613 else if (!xmlStrncmp(name
, BAD_CAST("onEnter"), 8)) {
616 else if (!xmlStrncmp(name
, BAD_CAST("onLeave"), 8)) {
619 else if (!xmlStrncmp(name
, BAD_CAST("door"), 5)) {
620 t
->door
= parseDoor((const char*)value
);
626 unsigned Area::splitTileFlags(const std::string strOfFlags
)
628 std::vector
<std::string
> strs
;
629 strs
= splitStr(strOfFlags
, ",");
631 unsigned flags
= 0x0;
632 BOOST_FOREACH(std::string str
, strs
)
634 // TODO: reimplement comparisons as a hash table
635 if (str
== "nowalk") {
642 Area::Door
* Area::parseDoor(const std::string dest
)
644 std::vector
<std::string
> strs
;
645 strs
= splitStr(dest
, ",");
647 // TODO: verify the validity of the input string... it's coming from
649 Door
* door
= new Door
;
650 door
->area
= strs
[0];
651 door
->coord
.x
= atol(strs
[1].c_str());
652 door
->coord
.y
= atol(strs
[2].c_str());
653 door
->coord
.z
= atol(strs
[3].c_str());
657 coord_t
Area::getDimensions() const
662 Area::Tile
* Area::getTile(coord_t c
)
664 return map
[c
.z
][c
.y
][c
.x
];