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.
96 player
->moveByTile(posMove
);
102 GameWindow
* window
= GameWindow::getWindow();
103 Gosu::Graphics
* graphics
= &window
->graphics();
104 Gosu::Transform trans
= translateCoords();
105 graphics
->pushTransform(trans
);
107 for (unsigned layer
= 0; layer
!= map
.size(); layer
++)
109 grid_t grid
= map
[layer
];
110 for (unsigned y
= 0; y
!= grid
.size(); y
++)
113 for (unsigned x
= 0; x
!= row
.size(); x
++)
115 // TODO support animations
117 Gosu::Image
* img
= tile
->type
->graphics
[0];
118 img
->draw(x
*img
->width(), y
*img
->height(), 0);
124 graphics
->popTransform();
127 //! Returns the number closest to x within the range [low, high].
129 \param low Lowest possible return.
130 \param x Value to be bounded.
131 \param high Highest possible return.
132 \return A number close to x.
133 \sa center() and translateCoords()
135 static double bound(double low
, double x
, double high
)
144 static double center(double w
, double g
, double p
)
146 return w
>g
? (w
-g
)/2.0 : bound(w
-g
, w
/2.0-p
, 0);
149 Gosu::Transform
Area::translateCoords()
151 GameWindow
* window
= GameWindow::getWindow();
152 Gosu::Graphics
* graphics
= &window
->graphics();
154 double tileWidth
= (double)tilesets
[0].tiledim
.x
;
155 double tileHeight
= (double)tilesets
[0].tiledim
.y
;
156 double windowWidth
= (double)graphics
->width() / tileWidth
;
157 double windowHeight
= (double)graphics
->height() / tileHeight
;
158 double gridWidth
= (double)dim
.x
;
159 double gridHeight
= (double)dim
.y
;
160 double playerX
= (double)player
->getCoordsByPixel().x
/ tileWidth
+ 0.5;
161 double playerY
= (double)player
->getCoordsByPixel().y
/ tileHeight
+ 0.5;
164 c
.x
= (long)(center(windowWidth
, gridWidth
, playerX
) * tileWidth
);
165 c
.y
= (long)(center(windowHeight
, gridHeight
, playerY
) * tileHeight
);
167 Gosu::Transform trans
= Gosu::translate((double)c
.x
, (double)c
.y
);
171 bool Area::needsRedraw() const
173 return player
->needsRedraw();
176 bool Area::processDescriptor()
178 xmlDoc
* doc
= rc
->getXMLDoc(descriptor
);
182 // use RAII to ensure doc is freed
183 boost::shared_ptr
<void> alwaysFreeTheDoc(doc
, xmlFreeDoc
);
185 // Iterate and process children of <map>
186 xmlNode
* root
= xmlDocGetRootElement(doc
); // <map> element
188 xmlChar
* width
= xmlGetProp(root
, BAD_CAST("width"));
189 xmlChar
* height
= xmlGetProp(root
, BAD_CAST("height"));
190 dim
.x
= atol((const char*)width
);
191 dim
.y
= atol((const char*)height
);
193 xmlNode
* child
= root
->xmlChildrenNode
;
194 for (; child
!= NULL
; child
= child
->next
) {
195 if (!xmlStrncmp(child
->name
, BAD_CAST("properties"), 11)) {
196 if (!processMapProperties(child
))
199 else if (!xmlStrncmp(child
->name
, BAD_CAST("tileset"), 8)) {
200 if (!processTileset(child
))
203 else if (!xmlStrncmp(child
->name
, BAD_CAST("layer"), 6)) {
204 if (!processLayer(child
))
207 else if (!xmlStrncmp(child
->name
, BAD_CAST("objectgroup"), 12)) {
208 if (!processObjectGroup(child
))
216 bool Area::processMapProperties(xmlNode
* node
)
221 <property name="areaspec" value="1"/>
222 <property name="author" value="Michael D. Reiley"/>
223 <property name="name" value="Baby's First Area"/>
224 <property name="music_loop" value="true"/>
225 <property name="music_main" value="wind.music"/>
226 <property name="onLoad" value="babysfirst_init()"/>
227 <property name="scripts" value="areainits.event,test.event"/>
231 xmlNode
* child
= node
->xmlChildrenNode
;
232 for (; child
!= NULL
; child
= child
->next
) {
233 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
234 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
235 if (!xmlStrncmp(name
, BAD_CAST("author"), 7))
236 author
= (const char*)value
;
237 else if (!xmlStrncmp(name
, BAD_CAST("name"), 5))
238 this->name
= (const char*)value
;
239 else if (!xmlStrncmp(name
, BAD_CAST("music_loop"), 11))
240 main
.loop
= parseBool((const char*)value
);
241 else if (!xmlStrncmp(name
, BAD_CAST("music_main"), 11))
242 main
.filename
= (const char*)value
;
243 else if (!xmlStrncmp(name
, BAD_CAST("onLoad"), 7))
244 onLoadEvents
= (const char*)value
;
245 else if (!xmlStrncmp(name
, BAD_CAST("scripts"), 8))
246 scripts
= (const char*)value
; // TODO split(), load
251 bool Area::processTileset(xmlNode
* node
)
255 <tileset firstgid="1" name="tiles.sheet" tilewidth="64" tileheight="64">
256 <image source="tiles.sheet" width="256" height="256"/>
263 xmlChar
* width
= xmlGetProp(node
, BAD_CAST("tilewidth"));
264 xmlChar
* height
= xmlGetProp(node
, BAD_CAST("tileheight"));
265 ts
.tiledim
.x
= atol((const char*)width
);
266 ts
.tiledim
.y
= atol((const char*)height
);
268 xmlNode
* child
= node
->xmlChildrenNode
;
269 for (; child
!= NULL
; child
= child
->next
) {
270 if (!xmlStrncmp(child
->name
, BAD_CAST("tile"), 5)) {
271 xmlChar
* idstr
= xmlGetProp(child
, BAD_CAST("id"));
272 unsigned id
= (unsigned)atoi((const char*)idstr
);
274 // Undeclared TileTypes have default properties.
275 while (ts
.defaults
.size() != id
) {
276 TileType tt
= defaultTileType(ts
.source
,
277 ts
.tiledim
, (int)ts
.defaults
.size());
278 ts
.defaults
.push_back(tt
);
281 // Handle explicit TileType
282 if (!processTileType(child
, ts
))
285 else if (!xmlStrncmp(child
->name
, BAD_CAST("image"), 6)) {
286 xmlChar
* source
= xmlGetProp(child
, BAD_CAST("source"));
287 ts
.source
= rc
->getBitmap((const char*)source
);
291 // Generate default tile types in range (m,n] where m is the last
292 // explicitly declared type and n is the number we require.
293 unsigned srcsz
= ts
.source
.width() * ts
.source
.height();
294 unsigned long tilesz
= (unsigned long)(ts
.tiledim
.x
* ts
.tiledim
.y
);
295 while (ts
.defaults
.size() != srcsz
/ tilesz
) {
296 TileType tt
= defaultTileType(ts
.source
,
297 ts
.tiledim
, (int)ts
.defaults
.size());
298 ts
.defaults
.push_back(tt
);
301 tilesets
.push_back(ts
);
305 Area::TileType
Area::defaultTileType(const Gosu::Bitmap source
, coord_t tiledim
,
308 unsigned x
= (unsigned)((tiledim
.x
* id
) % source
.width());
309 unsigned y
= (unsigned)((tiledim
.y
* id
) / source
.height() * tiledim
.y
); // ???
312 Gosu::Image
* img
= rc
->bitmapSection(source
, x
, y
,
313 (unsigned)tiledim
.x
, (unsigned)tiledim
.y
, true);
314 tt
.graphics
.push_back(img
);
321 bool Area::processTileType(xmlNode
* node
, Tileset
& ts
)
327 <property name="flags" value="nowalk"/>
328 <property name="onEnter" value="skid();speed(2)"/>
329 <property name="onLeave" value="undo()"/>
334 <property name="animated" value="1"/>
335 <property name="size" value="2"/>
336 <property name="speed" value="2"/>
341 // Initialize a default TileType, we'll build on that.
342 TileType tt
= defaultTileType(ts
.source
,
343 ts
.tiledim
, (int)ts
.defaults
.size());
345 xmlChar
* idstr
= xmlGetProp(node
, BAD_CAST("id"));
346 unsigned id
= (unsigned)atoi((const char*)idstr
);
347 if (id
!= ts
.defaults
.size()) {
348 // XXX we need to know the Area we're loading...
349 Log::err("unknown area", std::string("expected TileType id ") +
350 itostr((long)ts
.defaults
.size()) + ", but got " + itostr(id
));
354 xmlNode
* child
= node
->xmlChildrenNode
; // <properties>
355 child
= child
->xmlChildrenNode
; // <property>
356 for (; child
!= NULL
; child
= child
->next
) {
357 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
358 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
359 if (!xmlStrncmp(name
, BAD_CAST("flags"), 6)) {
360 tt
.flags
= splitTileFlags((const char*)value
);
362 else if (!xmlStrncmp(name
, BAD_CAST("onEnter"), 8)) {
365 else if (!xmlStrncmp(name
, BAD_CAST("onLeave"), 8)) {
368 else if (!xmlStrncmp(name
, BAD_CAST("animated"), 9)) {
369 tt
.animated
= parseBool((const char*)value
);
371 else if (!xmlStrncmp(name
, BAD_CAST("size"), 5)) {
374 else if (!xmlStrncmp(name
, BAD_CAST("speed"), 6)) {
375 tt
.ani_speed
= atof((const char*)value
);
379 ts
.defaults
.push_back(tt
);
383 bool Area::processLayer(xmlNode
* node
)
387 <layer name="Tiles0" width="5" height="5">
403 xmlChar
* width
= xmlGetProp(node
, BAD_CAST("width"));
404 xmlChar
* height
= xmlGetProp(node
, BAD_CAST("height"));
405 int x
= atoi((const char*)width
);
406 int y
= atoi((const char*)height
);
408 if (dim
.x
!= x
|| dim
.y
!= y
) {
409 // XXX we need to know the Area we're loading...
410 Log::err("unknown area", "layer x,y size != map x,y size");
414 xmlNode
* child
= node
->xmlChildrenNode
;
415 for (; child
!= NULL
; child
= child
->next
) {
416 if (!xmlStrncmp(child
->name
, BAD_CAST("properties"), 11)) {
417 if (!processLayerProperties(child
))
420 else if (!xmlStrncmp(child
->name
, BAD_CAST("data"), 5)) {
421 if (!processLayerData(child
))
428 bool Area::processLayerProperties(xmlNode
* node
)
433 <property name="layer" value="0"/>
437 xmlNode
* child
= node
->xmlChildrenNode
;
438 for (; child
!= NULL
; child
= child
->next
) {
439 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
440 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
441 if (!xmlStrncmp(name
, BAD_CAST("layer"), 6)) {
442 int depth
= atoi((const char*)value
);
443 if (depth
!= dim
.z
) {
444 Log::err("unknown area", "invalid layer depth");
453 bool Area::processLayerData(xmlNode
* node
)
471 xmlNode
* child
= node
->xmlChildrenNode
;
472 for (int i
= 1; child
!= NULL
; i
++, child
= child
->next
) {
473 if (!xmlStrncmp(child
->name
, BAD_CAST("tile"), 5)) {
474 xmlChar
* gidStr
= xmlGetProp(child
, BAD_CAST("gid"));
475 unsigned gid
= (unsigned)atoi((const char*)gidStr
)-1;
477 t
->type
= &tilesets
[0].defaults
[gid
]; // XXX can only access first tileset
481 if (row
.size() % dim
.x
== 0) {
493 bool Area::processObjectGroup(xmlNode
* node
)
497 <objectgroup name="Prop0" width="5" height="5">
499 <property name="layer" value="0"/>
501 <object name="tile2" type="Tile" gid="7" x="64" y="320">
503 <property name="onEnter" value="speed(0.5)"/>
504 <property name="onLeave" value="undo()"/>
505 <property name="door" value="grassfield.area,1,1,0"/>
506 <property name="flags" value="npc_nowalk"/>
512 xmlChar
* width
= xmlGetProp(node
, BAD_CAST("width"));
513 xmlChar
* height
= xmlGetProp(node
, BAD_CAST("height"));
514 int x
= atoi((const char*)width
);
515 int y
= atoi((const char*)height
);
519 if (dim
.x
!= x
|| dim
.y
!= y
) {
520 // XXX we need to know the Area we're loading...
521 Log::err("unknown area", "objectgroup x,y size != map x,y size");
525 xmlNode
* child
= node
->xmlChildrenNode
;
526 for (; child
!= NULL
; child
= child
->next
) {
527 if (!xmlStrncmp(child
->name
, BAD_CAST("properties"), 11)) {
528 if (!processObjectGroupProperties(child
, &zpos
))
531 else if (!xmlStrncmp(child
->name
, BAD_CAST("object"), 7)) {
532 if (zpos
== -1 || !processObject(child
, zpos
))
540 bool Area::processObjectGroupProperties(xmlNode
* node
, int* zpos
)
545 <property name="layer" value="0"/>
549 xmlNode
* child
= node
->xmlChildrenNode
;
550 for (; child
!= NULL
; child
= child
->next
) {
551 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
552 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
553 if (!xmlStrncmp(name
, BAD_CAST("layer"), 6)) {
554 int layer
= atoi((const char*)value
);
555 if (0 < layer
|| layer
>= (int)dim
.z
) {
556 // XXX we need to know the Area we're loading...
557 Log::err("unknown area",
558 "objectgroup must correspond with layer"
568 bool Area::processObject(xmlNode
* node
, int zpos
)
572 <object name="tile2" type="Tile" gid="7" x="64" y="320">
574 <property name="onEnter" value="speed(0.5)"/>
575 <property name="onLeave" value="undo()"/>
576 <property name="door" value="grassfield.area,1,1,0"/>
577 <property name="flags" value="npc_nowalk"/>
582 xmlChar
* type
= xmlGetProp(node
, BAD_CAST("type"));
583 if (xmlStrncmp(type
, BAD_CAST("Tile"), 5)) {
584 Log::err("unknown area", "object type must be Tile");
588 xmlChar
* xStr
= xmlGetProp(node
, BAD_CAST("x"));
589 xmlChar
* yStr
= xmlGetProp(node
, BAD_CAST("y"));
590 // XXX we ignore the object gid... is that okay?
592 // wouldn't have to access tilesets if we had tiledim ourselves
593 long x
= atol((const char*)xStr
) / tilesets
[0].tiledim
.x
;
594 long y
= atol((const char*)yStr
) / tilesets
[0].tiledim
.y
;
595 y
= y
- 1; // bug in tiled? y is 1 too high
597 // We know which Tile is being talked about now... yay
598 Tile
* t
= map
[zpos
][y
][x
];
600 xmlNode
* child
= node
->xmlChildrenNode
; // <properties>
601 child
= child
->xmlChildrenNode
; // <property>
602 for (; child
!= NULL
; child
= child
->next
) {
603 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
604 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
605 if (!xmlStrncmp(name
, BAD_CAST("flags"), 6)) {
606 t
->flags
= splitTileFlags((const char*)value
);
608 else if (!xmlStrncmp(name
, BAD_CAST("onEnter"), 8)) {
611 else if (!xmlStrncmp(name
, BAD_CAST("onLeave"), 8)) {
614 else if (!xmlStrncmp(name
, BAD_CAST("door"), 5)) {
615 t
->door
= parseDoor((const char*)value
);
616 t
->flags
|= npc_nowalk
;
622 unsigned Area::splitTileFlags(const std::string strOfFlags
)
624 std::vector
<std::string
> strs
;
625 strs
= splitStr(strOfFlags
, ",");
627 unsigned flags
= 0x0;
628 BOOST_FOREACH(std::string str
, strs
)
630 // TODO: reimplement comparisons as a hash table
631 if (str
== "nowalk") {
638 Area::Door
* Area::parseDoor(const std::string dest
)
640 std::vector
<std::string
> strs
;
641 strs
= splitStr(dest
, ",");
643 // TODO: verify the validity of the input string... it's coming from
645 Door
* door
= new Door
;
646 door
->area
= strs
[0];
647 door
->coord
.x
= atol(strs
[1].c_str());
648 door
->coord
.y
= atol(strs
[2].c_str());
649 door
->coord
.z
= atol(strs
[3].c_str());
653 coord_t
Area::getDimensions() const
658 Area::Tile
* Area::getTile(coord_t c
)
660 return map
[c
.z
][c
.y
][c
.x
];