1 /******************************
2 ** Tsunagari Tile Engine **
4 ** Copyright 2011 OmegaSDG **
5 ******************************/
7 #include <boost/algorithm/string.hpp>
8 #include <boost/foreach.hpp>
9 #include <boost/shared_ptr.hpp>
15 #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
, Entity
* player
, const std::string
& descriptor
)
25 : rc(rc
), player(player
), descriptor(descriptor
)
36 if (!processDescriptor()) // Try to load in descriptor.
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));
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
++)
66 for (unsigned int x
= 0; x
!= row
.size(); x
++)
68 // TODO support animations
70 Gosu::Image
* img
= tile
->type
->graphics
[0];
71 img
->draw(x
*img
->width(), y
*img
->height(), 0);
77 graphics
->popTransform();
80 static double bound(double low
, double x
, double high
)
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();
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;
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
);
116 bool Area::needsRedraw() const
118 return player
->needsRedraw();
121 bool Area::processDescriptor()
123 xmlDoc
* doc
= rc
->getXMLDoc(descriptor
);
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
))
144 else if (!xmlStrncmp(child
->name
, BAD_CAST("tileset"), 8)) {
145 if (!processTileset(child
))
148 else if (!xmlStrncmp(child
->name
, BAD_CAST("layer"), 6)) {
149 if (!processLayer(child
))
152 else if (!xmlStrncmp(child
->name
, BAD_CAST("objectgroup"), 12)) {
153 if (!processObjectGroup(child
))
161 bool Area::processMapProperties(xmlNode
* node
)
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"/>
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
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"/>
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
))
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
);
250 Area::TileType
Area::defaultTileType(const Gosu::Bitmap source
, coord_t tiledim
,
253 int x
= (tiledim
.x
* id
) % source
.width();
254 int y
= (tiledim
.y
* id
) / source
.height() * tiledim
.y
; // ???
257 Gosu::Image
* img
= rc
->bitmapSection(source
, x
, y
,
258 tiledim
.x
, tiledim
.y
, true);
259 tt
.graphics
.push_back(img
);
266 bool Area::processTileType(xmlNode
* node
, Tileset
& ts
)
272 <property name="flags" value="nowalk"/>
273 <property name="onEnter" value="skid();speed(2)"/>
274 <property name="onLeave" value="undo()"/>
279 <property name="animated" value="1"/>
280 <property name="size" value="2"/>
281 <property name="speed" value="2"/>
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
));
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)) {
310 else if (!xmlStrncmp(name
, BAD_CAST("onLeave"), 8)) {
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)) {
319 else if (!xmlStrncmp(name
, BAD_CAST("speed"), 6)) {
320 tt
.ani_speed
= atol((const char*)value
);
324 ts
.defaults
.push_back(tt
);
328 bool Area::processLayer(xmlNode
* node
)
332 <layer name="Tiles0" width="5" height="5">
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");
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
))
365 else if (!xmlStrncmp(child
->name
, BAD_CAST("data"), 5)) {
366 if (!processLayerData(child
))
373 bool Area::processLayerProperties(xmlNode
* node
)
378 <property name="layer" value="0"/>
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");
398 bool Area::processLayerData(xmlNode
* node
)
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;
422 t
->type
= &tilesets
[0].defaults
[gid
]; // XXX can only access first tileset
425 if (row
.size() % dim
.x
== 0) {
437 bool Area::processObjectGroup(xmlNode
* node
)
441 <objectgroup name="Prop0" width="5" height="5">
443 <property name="layer" value="0"/>
445 <object name="tile2" type="Tile" gid="7" x="64" y="320">
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"/>
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
);
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");
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
))
475 else if (!xmlStrncmp(child
->name
, BAD_CAST("object"), 7)) {
476 if (zpos
== -1 || !processObject(child
, zpos
))
484 bool Area::processObjectGroupProperties(xmlNode
* node
, int* zpos
)
489 <property name="layer" value="0"/>
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"
512 bool Area::processObject(xmlNode
* node
, int zpos
)
516 <object name="tile2" type="Tile" gid="7" x="64" y="320">
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"/>
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");
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)) {
555 else if (!xmlStrncmp(name
, BAD_CAST("onLeave"), 8)) {
558 else if (!xmlStrncmp(name
, BAD_CAST("door"), 5)) {
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") {
581 coord_t
Area::getDimensions() const
586 Area::Tile
* Area::getTile(coord_t c
)
588 return map
[c
.z
][c
.y
][c
.x
];