1 /******************************
2 ** Tsunagari Tile Engine **
4 ** Copyright 2011 OmegaSDG **
5 ******************************/
7 #include <boost/foreach.hpp>
8 #include <boost/shared_ptr.hpp>
9 #include <Gosu/Audio.hpp>
10 #include <Gosu/Graphics.hpp>
11 #include <Gosu/Image.hpp>
17 #include "resourcer.h"
21 /* NOTE: Tileset tiles start counting their positions from 0, while layer tiles
22 start counting from 1. I can't imagine why the author did this, but we
23 have to take it into account.
26 Area::Area(Resourcer
* rc
,
29 const std::string
& descriptor
)
30 : rc(rc
), world(world
), player(player
), descriptor(descriptor
),
33 dim
.x
= dim
.y
= dim
.z
= 0;
38 if (musicInst
&& musicInst
->playing())
41 // Delete each Tile. If a Tile has an allocated Door struct, delete
43 BOOST_FOREACH(grid_t grid
, map
) {
44 BOOST_FOREACH(row_t row
, grid
) {
45 BOOST_FOREACH(Tile
* tile
, row
) {
52 // Each Area owns its own Tileset objects. Delete tileset graphics.
53 BOOST_FOREACH(Tileset tileset
, tilesets
) {
54 delete tileset
.source
;
55 BOOST_FOREACH(TileType type
, tileset
.defaults
) {
56 BOOST_FOREACH(Gosu::Image
* img
, type
.graphics
) {
59 // TODO: delete TileEvents when we start using them
66 if (!processDescriptor())
70 musicInst
.reset(new Gosu::SampleInstance(introMusic
->play(1, 1, false)));
74 musicInst
.reset(new Gosu::SampleInstance(mainMusic
->play(1, 1, true)));
78 void Area::buttonDown(const Gosu::Button btn
)
80 bool attemptingMove
= false;
83 if (btn
== Gosu::kbRight
) {
84 posMove
= coord(1, 0, 0);
85 attemptingMove
= true;
87 else if (btn
== Gosu::kbLeft
) {
88 posMove
= coord(-1, 0, 0);
89 attemptingMove
= true;
91 else if (btn
== Gosu::kbUp
) {
92 posMove
= coord(0, -1, 0);
93 attemptingMove
= true;
95 else if (btn
== Gosu::kbDown
) {
96 posMove
= coord(0, 1, 0);
97 attemptingMove
= true;
101 player
->moveByTile(posMove
);
106 GameWindow
* window
= GameWindow::getWindow();
107 Gosu::Graphics
* graphics
= &window
->graphics();
108 Gosu::Transform trans
= translateCoords();
109 graphics
->pushTransform(trans
);
111 for (unsigned layer
= 0; layer
!= map
.size(); layer
++)
113 grid_t grid
= map
[layer
];
114 for (unsigned y
= 0; y
!= grid
.size(); y
++)
117 for (unsigned x
= 0; x
!= row
.size(); x
++)
119 // TODO support animations
121 Gosu::Image
* img
= tile
->type
->graphics
[0];
122 img
->draw(x
*img
->width(), y
*img
->height(), 0);
128 graphics
->popTransform();
131 bool Area::needsRedraw() const
133 return player
->needsRedraw();
138 if (onIntro
&& !musicInst
->playing()) {
140 musicInst
.reset(new Gosu::SampleInstance(mainMusic
->play(1, 1, true)));
144 //! Returns the number closest to x within the range [low, high].
146 \param low Lowest possible return.
147 \param x Value to be bounded.
148 \param high Highest possible return.
149 \return A number close to x.
150 \sa center() and translateCoords()
152 static double bound(double low
, double x
, double high
)
161 static double center(double w
, double g
, double p
)
163 return w
>g
? (w
-g
)/2.0 : bound(w
-g
, w
/2.0-p
, 0);
166 Gosu::Transform
Area::translateCoords()
168 GameWindow
* window
= GameWindow::getWindow();
169 Gosu::Graphics
* graphics
= &window
->graphics();
171 double tileWidth
= (double)tilesets
[0].tiledim
.x
;
172 double tileHeight
= (double)tilesets
[0].tiledim
.y
;
173 double windowWidth
= (double)graphics
->width() / tileWidth
;
174 double windowHeight
= (double)graphics
->height() / tileHeight
;
175 double gridWidth
= (double)dim
.x
;
176 double gridHeight
= (double)dim
.y
;
177 double playerX
= (double)player
->getCoordsByPixel().x
/ tileWidth
+ 0.5;
178 double playerY
= (double)player
->getCoordsByPixel().y
/ tileHeight
+ 0.5;
181 c
.x
= (long)(center(windowWidth
, gridWidth
, playerX
) * tileWidth
);
182 c
.y
= (long)(center(windowHeight
, gridHeight
, playerY
) * tileHeight
);
184 Gosu::Transform trans
= Gosu::translate((double)c
.x
, (double)c
.y
);
188 bool Area::processDescriptor()
190 XMLDocRef doc
= rc
->getXMLDoc(descriptor
, "dtd/area.dtd");
194 // Iterate and process children of <map>
195 xmlNode
* root
= xmlDocGetRootElement(doc
.get()); // <map> element
197 xmlChar
* width
= xmlGetProp(root
, BAD_CAST("width"));
198 xmlChar
* height
= xmlGetProp(root
, BAD_CAST("height"));
199 dim
.x
= atol((const char*)width
);
200 dim
.y
= atol((const char*)height
);
202 xmlNode
* child
= root
->xmlChildrenNode
;
203 for (; child
!= NULL
; child
= child
->next
) {
204 if (!xmlStrncmp(child
->name
, BAD_CAST("properties"), 11)) {
205 if (!processMapProperties(child
))
208 else if (!xmlStrncmp(child
->name
, BAD_CAST("tileset"), 8)) {
209 if (!processTileset(child
))
212 else if (!xmlStrncmp(child
->name
, BAD_CAST("layer"), 6)) {
213 if (!processLayer(child
))
216 else if (!xmlStrncmp(child
->name
, BAD_CAST("objectgroup"), 12)) {
217 if (!processObjectGroup(child
))
225 bool Area::processMapProperties(xmlNode
* node
)
230 <property name="areaspec" value="1"/>
231 <property name="author" value="Michael D. Reiley"/>
232 <property name="name" value="Baby's First Area"/>
233 <property name="intro_music" value="intro.music"/>
234 <property name="main_music" value="wind.music"/>
235 <property name="onLoad" value="babysfirst_init()"/>
236 <property name="scripts" value="areainits.event,test.event"/>
240 xmlNode
* child
= node
->xmlChildrenNode
;
241 for (; child
!= NULL
; child
= child
->next
) {
242 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
243 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
244 if (!xmlStrncmp(name
, BAD_CAST("author"), 7))
245 author
= (const char*)value
;
246 else if (!xmlStrncmp(name
, BAD_CAST("name"), 5))
247 this->name
= (const char*)value
;
248 else if (!xmlStrncmp(name
, BAD_CAST("intro_music"), 12))
249 introMusic
= rc
->getSample((const char*)value
);
250 else if (!xmlStrncmp(name
, BAD_CAST("main_music"), 11))
251 mainMusic
= rc
->getSample((const char*)value
);
252 else if (!xmlStrncmp(name
, BAD_CAST("onLoad"), 7))
253 onLoadEvents
= (const char*)value
;
254 else if (!xmlStrncmp(name
, BAD_CAST("scripts"), 8))
255 scripts
= (const char*)value
; // TODO split(), load
260 bool Area::processTileset(xmlNode
* node
)
264 <tileset firstgid="1" name="tiles.sheet" tilewidth="64" tileheight="64">
265 <image source="tiles.sheet" width="256" height="256"/>
272 ts
.source
= new Gosu::Bitmap
;
273 xmlChar
* width
= xmlGetProp(node
, BAD_CAST("tilewidth"));
274 xmlChar
* height
= xmlGetProp(node
, BAD_CAST("tileheight"));
275 ts
.tiledim
.x
= atol((const char*)width
);
276 ts
.tiledim
.y
= atol((const char*)height
);
278 xmlNode
* child
= node
->xmlChildrenNode
;
279 for (; child
!= NULL
; child
= child
->next
) {
280 if (!xmlStrncmp(child
->name
, BAD_CAST("tile"), 5)) {
281 xmlChar
* idstr
= xmlGetProp(child
, BAD_CAST("id"));
282 unsigned id
= (unsigned)atoi((const char*)idstr
);
284 // Undeclared TileTypes have default properties.
285 while (ts
.defaults
.size() != id
) {
286 TileType tt
= defaultTileType(ts
.source
,
287 ts
.tiledim
, (int)ts
.defaults
.size());
288 ts
.defaults
.push_back(tt
);
291 // Handle explicit TileType
292 if (!processTileType(child
, ts
))
295 else if (!xmlStrncmp(child
->name
, BAD_CAST("image"), 6)) {
296 xmlChar
* source
= xmlGetProp(child
, BAD_CAST("source"));
297 rc
->getBitmap(*ts
.source
, (const char*)source
);
301 // Generate default tile types in range (m,n] where m is the last
302 // explicitly declared type and n is the number we require.
303 unsigned srcsz
= ts
.source
->width() * ts
.source
->height();
304 unsigned long tilesz
= (unsigned long)(ts
.tiledim
.x
* ts
.tiledim
.y
);
305 while (ts
.defaults
.size() != srcsz
/ tilesz
) {
306 TileType tt
= defaultTileType(ts
.source
,
307 ts
.tiledim
, (int)ts
.defaults
.size());
308 ts
.defaults
.push_back(tt
);
311 tilesets
.push_back(ts
);
315 Area::TileType
Area::defaultTileType(const Gosu::Bitmap
* source
,
316 coord_t tiledim
, int id
)
318 unsigned x
= (unsigned)((tiledim
.x
* id
) % source
->width());
319 unsigned y
= (unsigned)((tiledim
.y
* id
) / source
->width() * tiledim
.y
); // ???
322 Gosu::Image
* img
= rc
->bitmapSection(*source
, x
, y
,
323 (unsigned)tiledim
.x
, (unsigned)tiledim
.y
, true);
324 tt
.graphics
.push_back(img
);
331 bool Area::processTileType(xmlNode
* node
, Tileset
& ts
)
337 <property name="flags" value="nowalk"/>
338 <property name="onEnter" value="skid();speed(2)"/>
339 <property name="onLeave" value="undo()"/>
344 <property name="animated" value="1"/>
345 <property name="size" value="2"/>
346 <property name="speed" value="2"/>
351 // Initialize a default TileType, we'll build on that.
352 TileType tt
= defaultTileType(ts
.source
,
353 ts
.tiledim
, (int)ts
.defaults
.size());
355 xmlChar
* idstr
= xmlGetProp(node
, BAD_CAST("id"));
356 unsigned id
= (unsigned)atoi((const char*)idstr
);
357 if (id
!= ts
.defaults
.size()) {
358 // XXX we need to know the Area we're loading...
359 Log::err("unknown area", std::string("expected TileType id ") +
360 itostr((long)ts
.defaults
.size()) + ", but got " + itostr(id
));
364 xmlNode
* child
= node
->xmlChildrenNode
; // <properties>
365 child
= child
->xmlChildrenNode
; // <property>
366 for (; child
!= NULL
; child
= child
->next
) {
367 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
368 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
369 if (!xmlStrncmp(name
, BAD_CAST("flags"), 6)) {
370 tt
.flags
= splitTileFlags((const char*)value
);
372 else if (!xmlStrncmp(name
, BAD_CAST("onEnter"), 8)) {
375 else if (!xmlStrncmp(name
, BAD_CAST("onLeave"), 8)) {
378 else if (!xmlStrncmp(name
, BAD_CAST("animated"), 9)) {
379 tt
.animated
= parseBool((const char*)value
);
381 else if (!xmlStrncmp(name
, BAD_CAST("size"), 5)) {
384 else if (!xmlStrncmp(name
, BAD_CAST("speed"), 6)) {
385 tt
.ani_speed
= atof((const char*)value
);
389 ts
.defaults
.push_back(tt
);
393 bool Area::processLayer(xmlNode
* node
)
397 <layer name="Tiles0" width="5" height="5">
413 xmlChar
* width
= xmlGetProp(node
, BAD_CAST("width"));
414 xmlChar
* height
= xmlGetProp(node
, BAD_CAST("height"));
415 int x
= atoi((const char*)width
);
416 int y
= atoi((const char*)height
);
418 if (dim
.x
!= x
|| dim
.y
!= y
) {
419 // XXX we need to know the Area we're loading...
420 Log::err("unknown area", "layer x,y size != map x,y size");
424 xmlNode
* child
= node
->xmlChildrenNode
;
425 for (; child
!= NULL
; child
= child
->next
) {
426 if (!xmlStrncmp(child
->name
, BAD_CAST("properties"), 11)) {
427 if (!processLayerProperties(child
))
430 else if (!xmlStrncmp(child
->name
, BAD_CAST("data"), 5)) {
431 if (!processLayerData(child
))
438 bool Area::processLayerProperties(xmlNode
* node
)
443 <property name="layer" value="0"/>
447 xmlNode
* child
= node
->xmlChildrenNode
;
448 for (; child
!= NULL
; child
= child
->next
) {
449 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
450 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
451 if (!xmlStrncmp(name
, BAD_CAST("layer"), 6)) {
452 int depth
= atoi((const char*)value
);
453 if (depth
!= dim
.z
) {
454 Log::err("unknown area", "invalid layer depth");
463 bool Area::processLayerData(xmlNode
* node
)
484 xmlNode
* child
= node
->xmlChildrenNode
;
485 for (int i
= 1; child
!= NULL
; i
++, child
= child
->next
) {
486 if (!xmlStrncmp(child
->name
, BAD_CAST("tile"), 5)) {
487 xmlChar
* gidStr
= xmlGetProp(child
, BAD_CAST("gid"));
488 unsigned gid
= (unsigned)atoi((const char*)gidStr
)-1;
490 t
->type
= &tilesets
[0].defaults
[gid
]; // XXX can only access first tileset
494 if (row
.size() % dim
.x
== 0) {
507 bool Area::processObjectGroup(xmlNode
* node
)
511 <objectgroup name="Prop0" width="5" height="5">
513 <property name="layer" value="0"/>
515 <object name="tile2" type="Tile" gid="7" x="64" y="320">
517 <property name="onEnter" value="speed(0.5)"/>
518 <property name="onLeave" value="undo()"/>
519 <property name="door" value="grassfield.area,1,1,0"/>
520 <property name="flags" value="npc_nowalk"/>
526 xmlChar
* width
= xmlGetProp(node
, BAD_CAST("width"));
527 xmlChar
* height
= xmlGetProp(node
, BAD_CAST("height"));
528 int x
= atoi((const char*)width
);
529 int y
= atoi((const char*)height
);
533 if (dim
.x
!= x
|| dim
.y
!= y
) {
534 // XXX we need to know the Area we're loading...
535 Log::err("unknown area", "objectgroup x,y size != map x,y size");
539 xmlNode
* child
= node
->xmlChildrenNode
;
540 for (; child
!= NULL
; child
= child
->next
) {
541 if (!xmlStrncmp(child
->name
, BAD_CAST("properties"), 11)) {
542 if (!processObjectGroupProperties(child
, &zpos
))
545 else if (!xmlStrncmp(child
->name
, BAD_CAST("object"), 7)) {
546 if (zpos
== -1 || !processObject(child
, zpos
))
554 bool Area::processObjectGroupProperties(xmlNode
* node
, int* zpos
)
559 <property name="layer" value="0"/>
563 xmlNode
* child
= node
->xmlChildrenNode
;
564 for (; child
!= NULL
; child
= child
->next
) {
565 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
566 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
567 if (!xmlStrncmp(name
, BAD_CAST("layer"), 6)) {
568 int layer
= atoi((const char*)value
);
569 if (0 < layer
|| layer
>= (int)dim
.z
) {
570 // XXX we need to know the Area we're loading...
571 Log::err("unknown area",
572 "objectgroup must correspond with layer"
582 bool Area::processObject(xmlNode
* node
, int zpos
)
586 <object name="tile2" type="Tile" gid="7" x="64" y="320">
588 <property name="onEnter" value="speed(0.5)"/>
589 <property name="onLeave" value="undo()"/>
590 <property name="door" value="grassfield.area,1,1,0"/>
591 <property name="flags" value="npc_nowalk"/>
596 xmlChar
* type
= xmlGetProp(node
, BAD_CAST("type"));
597 if (xmlStrncmp(type
, BAD_CAST("Tile"), 5)) {
598 Log::err("unknown area", "object type must be Tile");
602 xmlChar
* xStr
= xmlGetProp(node
, BAD_CAST("x"));
603 xmlChar
* yStr
= xmlGetProp(node
, BAD_CAST("y"));
604 // XXX we ignore the object gid... is that okay?
606 // wouldn't have to access tilesets if we had tiledim ourselves
607 long x
= atol((const char*)xStr
) / tilesets
[0].tiledim
.x
;
608 long y
= atol((const char*)yStr
) / tilesets
[0].tiledim
.y
;
609 y
= y
- 1; // bug in tiled? y is 1 too high
611 // We know which Tile is being talked about now... yay
612 Tile
* t
= map
[zpos
][y
][x
];
614 xmlNode
* child
= node
->xmlChildrenNode
; // <properties>
615 child
= child
->xmlChildrenNode
; // <property>
616 for (; child
!= NULL
; child
= child
->next
) {
617 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
618 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
619 if (!xmlStrncmp(name
, BAD_CAST("flags"), 6)) {
620 t
->flags
= splitTileFlags((const char*)value
);
622 else if (!xmlStrncmp(name
, BAD_CAST("onEnter"), 8)) {
625 else if (!xmlStrncmp(name
, BAD_CAST("onLeave"), 8)) {
628 else if (!xmlStrncmp(name
, BAD_CAST("door"), 5)) {
629 t
->door
= parseDoor((const char*)value
);
630 t
->flags
|= npc_nowalk
;
636 unsigned Area::splitTileFlags(const std::string strOfFlags
)
638 std::vector
<std::string
> strs
;
639 strs
= splitStr(strOfFlags
, ",");
641 unsigned flags
= 0x0;
642 BOOST_FOREACH(std::string str
, strs
)
644 // TODO: reimplement comparisons as a hash table
645 if (str
== "nowalk") {
652 Area::Door
* Area::parseDoor(const std::string dest
)
654 std::vector
<std::string
> strs
;
655 strs
= splitStr(dest
, ",");
657 // TODO: verify the validity of the input string... it's coming from
659 Door
* door
= new Door
;
660 door
->area
= strs
[0];
661 door
->coord
.x
= atol(strs
[1].c_str());
662 door
->coord
.y
= atol(strs
[2].c_str());
663 door
->coord
.z
= atol(strs
[3].c_str());
667 coord_t
Area::getDimensions() const
672 coord_t
Area::getTileDimensions() const
674 return tilesets
[0].tiledim
;
677 Area::Tile
* Area::getTile(coord_t c
)
679 return map
[c
.z
][c
.y
][c
.x
];