1 /******************************
2 ** Tsunagari Tile Engine **
4 ** Copyright 2011 OmegaSDG **
5 ******************************/
9 #include <boost/foreach.hpp>
10 #include <boost/shared_ptr.hpp>
11 #include <Gosu/Graphics.hpp>
12 #include <Gosu/Image.hpp>
13 #include <Gosu/Math.hpp>
14 #include <Gosu/Timing.hpp>
20 #include "resourcer.h"
24 /* NOTE: In the TMX map format used by Tiled, tileset tiles start counting
25 their Y-positions from 0, while layer tiles start counting from 1. I
26 can't imagine why the author did this, but we have to take it into
30 Area::Area(Resourcer
* rc
,
33 const std::string
& descriptor
)
34 : rc(rc
), world(world
), player(player
), descriptor(descriptor
),
37 dim
.x
= dim
.y
= dim
.z
= 0;
42 if (musicInst
&& musicInst
->playing())
48 if (!processDescriptor())
52 musicInst
.reset(introMusic
->play(1, 1, false));
56 musicInst
.reset(mainMusic
->play(1, 1, true));
61 void Area::buttonDown(const Gosu::Button btn
)
63 if (btn
== Gosu::kbRight
)
64 player
->startMovement(coord(1, 0, 0));
65 else if (btn
== Gosu::kbLeft
)
66 player
->startMovement(coord(-1, 0, 0));
67 else if (btn
== Gosu::kbUp
)
68 player
->startMovement(coord(0, -1, 0));
69 else if (btn
== Gosu::kbDown
)
70 player
->startMovement(coord(0, 1, 0));
73 void Area::buttonUp(const Gosu::Button btn
)
75 if (btn
== Gosu::kbRight
)
76 player
->stopMovement(coord(1, 0, 0));
77 else if (btn
== Gosu::kbLeft
)
78 player
->stopMovement(coord(-1, 0, 0));
79 else if (btn
== Gosu::kbUp
)
80 player
->stopMovement(coord(0, -1, 0));
81 else if (btn
== Gosu::kbDown
)
82 player
->stopMovement(coord(0, 1, 0));
87 Gosu::Graphics
& graphics
= GameWindow::getWindow().graphics();
88 const Gosu::Transform trans
= viewportTransform();
89 graphics
.pushTransform(trans
);
94 graphics
.popTransform();
97 void Area::drawTiles()
99 // Calculate frame to show for each type of tile
100 int millis
= GameWindow::getWindow().time();
101 BOOST_FOREACH(TileSet
& set
, tilesets
)
102 BOOST_FOREACH(TileType
& type
, set
.tileTypes
)
103 type
.anim
.updateFrame(millis
);
106 for (unsigned z
= 0; z
!= map
.size(); z
++) {
107 const grid_t
& grid
= map
[z
];
108 for (unsigned y
= 0; y
!= grid
.size(); y
++) {
109 const row_t
& row
= grid
[y
];
110 for (unsigned x
= 0; x
!= row
.size(); x
++) {
111 const Tile
& tile
= row
[x
];
112 const TileType
* type
= tile
.type
;
113 const Gosu::Image
* img
= type
->anim
.frame();
114 img
->draw(x
*img
->width(), y
*img
->height(), 0);
120 void Area::drawEntities()
125 bool Area::needsRedraw() const
127 if (player
->needsRedraw())
130 // Do any onscreen tile types need to update their animations?
131 int millis
= GameWindow::getWindow().time();
132 BOOST_FOREACH(const TileSet
& set
, tilesets
)
133 BOOST_FOREACH(const TileType
& type
, set
.tileTypes
)
134 if (type
.anim
.needsRedraw(millis
) &&
135 tileTypeOnScreen(type
))
140 void Area::update(unsigned long dt
)
142 if (onIntro
&& !musicInst
->playing()) {
144 musicInst
.reset(mainMusic
->play(1, 1, true));
149 coord_t
Area::getDimensions() const
154 coord_t
Area::getTileDimensions() const
156 return tilesets
[0].tileDim
; // XXX only considers first tileset
159 const Area::Tile
& Area::getTile(coord_t c
) const
161 return map
[c
.z
][c
.y
][c
.x
];
164 Area::Tile
& Area::getTile(coord_t c
)
166 return map
[c
.z
][c
.y
][c
.x
];
169 static double center(double w
, double g
, double p
)
171 return w
>g
? (w
-g
)/2.0 : Gosu::boundBy(w
/2.0-p
, w
-g
, 0.0);
174 const coord_t
Area::viewportOffset() const
176 const Gosu::Graphics
& graphics
= GameWindow::getWindow().graphics();
177 const double tileWidth
= (double)tilesets
[0].tileDim
.x
;
178 const double tileHeight
= (double)tilesets
[0].tileDim
.y
;
179 const double windowWidth
= (double)graphics
.width() / tileWidth
;
180 const double windowHeight
= (double)graphics
.height() / tileHeight
;
181 const double gridWidth
= (double)dim
.x
;
182 const double gridHeight
= (double)dim
.y
;
183 const double playerX
= (double)player
->getCoordsByPixel().x
/
185 const double playerY
= (double)player
->getCoordsByPixel().y
/
189 c
.x
= (long)(center(windowWidth
, gridWidth
, playerX
) * tileWidth
);
190 c
.y
= (long)(center(windowHeight
, gridHeight
, playerY
) * tileHeight
);
196 const Gosu::Transform
Area::viewportTransform() const
198 const coord_t c
= viewportOffset();
199 return Gosu::translate((double)c
.x
, (double)c
.y
);
202 cube_t
Area::visibleTiles() const
204 const Gosu::Graphics
& graphics
= GameWindow::getWindow().graphics();
205 const long tileWidth
= tilesets
[0].tileDim
.x
;
206 const long tileHeight
= tilesets
[0].tileDim
.y
;
207 const int windowWidth
= graphics
.width();
208 const int windowHeight
= graphics
.height();
209 const coord_t off
= viewportOffset();
211 const long x1
= -off
.x
/ tileWidth
;
212 const long y1
= -off
.y
/ tileHeight
;
213 const long x2
= (long)ceil((double)(windowWidth
- off
.x
) /
215 const long y2
= (long)ceil((double)(windowHeight
- off
.y
) /
218 // Does the entire width or height of the map fit onscreen?
219 if (x1
>= 0 && y1
>= 0)
220 return cube(x1
, y1
, 0, x2
, y2
, 1);
222 return cube(x1
, 0, 0, x2
, dim
.y
, 1);
224 return cube(0, y1
, 0, dim
.x
, y2
, 1);
226 return cube(0, 0, 0, dim
.x
, dim
.y
, 1);
229 bool Area::tileTypeOnScreen(const Area::TileType
& search
) const
231 const cube_t tiles
= visibleTiles();
232 for (long z
= tiles
.z1
; z
!= tiles
.z2
; z
++) {
233 for (long y
= tiles
.y1
; y
!= tiles
.y2
; y
++) {
234 for (long x
= tiles
.x1
; x
!= tiles
.x2
; x
++) {
235 const Tile
& tile
= map
[z
][y
][x
];
236 const TileType
* type
= tile
.type
;
245 bool Area::processDescriptor()
247 XMLDocRef doc
= rc
->getXMLDoc(descriptor
, "area.dtd");
251 // Iterate and process children of <map>
252 xmlNode
* root
= xmlDocGetRootElement(doc
.get()); // <map> element
254 xmlChar
* width
= xmlGetProp(root
, BAD_CAST("width"));
255 xmlChar
* height
= xmlGetProp(root
, BAD_CAST("height"));
256 dim
.x
= atol((const char*)width
);
257 dim
.y
= atol((const char*)height
);
259 xmlNode
* child
= root
->xmlChildrenNode
;
260 for (; child
!= NULL
; child
= child
->next
) {
261 if (!xmlStrncmp(child
->name
, BAD_CAST("properties"), 11)) {
262 if (!processMapProperties(child
))
265 else if (!xmlStrncmp(child
->name
, BAD_CAST("tileset"), 8)) {
266 if (!processTileSet(child
))
269 else if (!xmlStrncmp(child
->name
, BAD_CAST("layer"), 6)) {
270 if (!processLayer(child
))
273 else if (!xmlStrncmp(child
->name
, BAD_CAST("objectgroup"), 12)) {
274 if (!processObjectGroup(child
))
282 bool Area::processMapProperties(xmlNode
* node
)
287 <property name="areaspec" value="1"/>
288 <property name="author" value="Michael D. Reiley"/>
289 <property name="name" value="Baby's First Area"/>
290 <property name="intro_music" value="intro.music"/>
291 <property name="main_music" value="wind.music"/>
292 <property name="onLoad" value="babysfirst_init()"/>
293 <property name="scripts" value="areainits.event,test.event"/>
297 xmlNode
* child
= node
->xmlChildrenNode
;
298 for (; child
!= NULL
; child
= child
->next
) {
299 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
300 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
301 if (!xmlStrncmp(name
, BAD_CAST("author"), 7))
302 author
= (const char*)value
;
303 else if (!xmlStrncmp(name
, BAD_CAST("name"), 5))
304 this->name
= (const char*)value
;
305 else if (!xmlStrncmp(name
, BAD_CAST("intro_music"), 12))
306 introMusic
= rc
->getSample((const char*)value
);
307 else if (!xmlStrncmp(name
, BAD_CAST("main_music"), 11))
308 mainMusic
= rc
->getSample((const char*)value
);
309 else if (!xmlStrncmp(name
, BAD_CAST("onLoad"), 7))
310 onLoadEvents
= (const char*)value
;
311 else if (!xmlStrncmp(name
, BAD_CAST("scripts"), 8))
312 scripts
= (const char*)value
; // TODO split(), load
317 bool Area::processTileSet(xmlNode
* node
)
321 <tileset firstgid="1" name="tiles.sheet" tilewidth="64" tileheight="64">
322 <image source="tiles.sheet" width="256" height="256"/>
329 xmlChar
* width
= xmlGetProp(node
, BAD_CAST("tilewidth"));
330 xmlChar
* height
= xmlGetProp(node
, BAD_CAST("tileheight"));
331 long x
= ts
.tileDim
.x
= atol((const char*)width
);
332 long y
= ts
.tileDim
.y
= atol((const char*)height
);
334 xmlNode
* child
= node
->xmlChildrenNode
;
335 for (; child
!= NULL
; child
= child
->next
) {
336 if (!xmlStrncmp(child
->name
, BAD_CAST("tile"), 5)) {
337 xmlChar
* idstr
= xmlGetProp(child
, BAD_CAST("id"));
338 unsigned id
= (unsigned)atoi((const char*)idstr
);
340 // Undeclared TileTypes have default properties.
341 while (ts
.tileTypes
.size() != id
) {
342 TileType tt
= defaultTileType(ts
);
343 ts
.tileTypes
.push_back(tt
);
346 // Handle explicit TileType
347 if (!processTileType(child
, ts
))
350 else if (!xmlStrncmp(child
->name
, BAD_CAST("image"), 6)) {
351 const char* source
= (const char*)xmlGetProp(child
,
353 rc
->getTiledImage(ts
.tiles
, source
,
354 (unsigned)x
, (unsigned)y
, true);
358 while (ts
.tiles
.size()) {
359 TileType tt
= defaultTileType(ts
);
360 ts
.tileTypes
.push_back(tt
);
363 tilesets
.push_back(ts
);
367 Area::TileType
Area::defaultTileType(TileSet
& set
)
370 type
.anim
.addFrame(set
.tiles
.front());
371 set
.tiles
.pop_front();
375 bool Area::processTileType(xmlNode
* node
, TileSet
& set
)
381 <property name="flags" value="nowalk"/>
382 <property name="onEnter" value="skid();speed(2)"/>
383 <property name="onLeave" value="undo()"/>
388 <property name="animated" value="1"/>
389 <property name="size" value="2"/>
390 <property name="speed" value="2"/>
395 // Initialize a default TileType, we'll build on that.
396 TileType type
= defaultTileType(set
);
398 xmlChar
* idstr
= xmlGetProp(node
, BAD_CAST("id"));
399 unsigned id
= (unsigned)atoi((const char*)idstr
); // atoi
400 long expectedId
= set
.tileTypes
.size();
401 if (id
!= expectedId
) {
402 Log::err(descriptor
, std::string("expected TileType id ") +
403 itostr(expectedId
) + ", but got " +
408 xmlNode
* child
= node
->xmlChildrenNode
; // <properties>
409 child
= child
->xmlChildrenNode
; // <property>
410 for (; child
!= NULL
; child
= child
->next
) {
411 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
412 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
413 if (!xmlStrncmp(name
, BAD_CAST("flags"), 6)) {
414 type
.flags
= splitTileFlags((const char*)value
);
416 else if (!xmlStrncmp(name
, BAD_CAST("onEnter"), 8)) {
419 else if (!xmlStrncmp(name
, BAD_CAST("onLeave"), 8)) {
422 else if (!xmlStrncmp(name
, BAD_CAST("animated"), 9)) {
424 // type.animated = parseBool((const char*)value);
426 else if (!xmlStrncmp(name
, BAD_CAST("size"), 5)) {
427 int size
= atoi((const char*)value
); // atoi
429 // Add size-1 more frames to our animation.
430 // We already have one from defaultTileType.
431 for (int i
= 1; i
< size
; i
++) {
432 if (set
.tiles
.empty()) {
433 Log::err(descriptor
, "ran out of tiles"
434 "/frames for animated tile");
437 type
.anim
.addFrame(set
.tiles
.front());
438 set
.tiles
.pop_front();
441 else if (!xmlStrncmp(name
, BAD_CAST("speed"), 6)) {
442 int len
= (int)(1000.0/atof((const char*)value
));
443 type
.anim
.setFrameLen(len
);
447 set
.tileTypes
.push_back(type
);
451 bool Area::processLayer(xmlNode
* node
)
455 <layer name="Tiles0" width="5" height="5">
471 xmlChar
* width
= xmlGetProp(node
, BAD_CAST("width"));
472 xmlChar
* height
= xmlGetProp(node
, BAD_CAST("height"));
473 int x
= atoi((const char*)width
);
474 int y
= atoi((const char*)height
);
476 if (dim
.x
!= x
|| dim
.y
!= y
) {
477 Log::err(descriptor
, "layer x,y size != map x,y size");
481 xmlNode
* child
= node
->xmlChildrenNode
;
482 for (; child
!= NULL
; child
= child
->next
) {
483 if (!xmlStrncmp(child
->name
, BAD_CAST("properties"), 11)) {
484 if (!processLayerProperties(child
))
487 else if (!xmlStrncmp(child
->name
, BAD_CAST("data"), 5)) {
488 if (!processLayerData(child
))
495 bool Area::processLayerProperties(xmlNode
* node
)
500 <property name="layer" value="0"/>
504 xmlNode
* child
= node
->xmlChildrenNode
;
505 for (; child
!= NULL
; child
= child
->next
) {
506 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
507 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
508 if (!xmlStrncmp(name
, BAD_CAST("layer"), 6)) {
509 int depth
= atoi((const char*)value
);
510 if (depth
!= dim
.z
) {
511 Log::err(descriptor
, "invalid layer depth");
520 bool Area::processLayerData(xmlNode
* node
)
541 xmlNode
* child
= node
->xmlChildrenNode
;
542 for (int i
= 1; child
!= NULL
; i
++, child
= child
->next
) {
543 if (!xmlStrncmp(child
->name
, BAD_CAST("tile"), 5)) {
544 xmlChar
* gidStr
= xmlGetProp(child
, BAD_CAST("gid"));
545 unsigned gid
= (unsigned)atoi((const char*)gidStr
)-1;
547 // XXX can only access first tileset
548 TileType
* type
= &tilesets
[0].tileTypes
[gid
];
553 type
->allOfType
.push_back(&t
);
555 if (row
.size() % dim
.x
== 0) {
568 bool Area::processObjectGroup(xmlNode
* node
)
572 <objectgroup name="Prop0" width="5" height="5">
574 <property name="layer" value="0"/>
576 <object name="tile2" type="Tile" gid="7" x="64" y="320">
578 <property name="onEnter" value="speed(0.5)"/>
579 <property name="onLeave" value="undo()"/>
580 <property name="door" value="grassfield.area,1,1,0"/>
581 <property name="flags" value="npc_nowalk"/>
587 xmlChar
* width
= xmlGetProp(node
, BAD_CAST("width"));
588 xmlChar
* height
= xmlGetProp(node
, BAD_CAST("height"));
589 int x
= atoi((const char*)width
);
590 int y
= atoi((const char*)height
);
594 if (dim
.x
!= x
|| dim
.y
!= y
) {
595 Log::err(descriptor
, "objectgroup x,y size != map x,y size");
599 xmlNode
* child
= node
->xmlChildrenNode
;
600 for (; child
!= NULL
; child
= child
->next
) {
601 if (!xmlStrncmp(child
->name
, BAD_CAST("properties"), 11)) {
602 if (!processObjectGroupProperties(child
, &zpos
))
605 else if (!xmlStrncmp(child
->name
, BAD_CAST("object"), 7)) {
606 if (zpos
== -1 || !processObject(child
, zpos
))
614 bool Area::processObjectGroupProperties(xmlNode
* node
, int* zpos
)
619 <property name="layer" value="0"/>
623 xmlNode
* child
= node
->xmlChildrenNode
;
624 for (; child
!= NULL
; child
= child
->next
) {
625 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
626 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
627 if (!xmlStrncmp(name
, BAD_CAST("layer"), 6)) {
628 int layer
= atoi((const char*)value
);
629 if (0 < layer
|| layer
>= (int)dim
.z
) {
631 "objectgroup must correspond with layer"
641 bool Area::processObject(xmlNode
* node
, int zpos
)
645 <object name="tile2" type="Tile" gid="7" x="64" y="320">
647 <property name="onEnter" value="speed(0.5)"/>
648 <property name="onLeave" value="undo()"/>
649 <property name="door" value="grassfield.area,1,1,0"/>
650 <property name="flags" value="npc_nowalk"/>
655 xmlChar
* type
= xmlGetProp(node
, BAD_CAST("type"));
656 if (xmlStrncmp(type
, BAD_CAST("Tile"), 5)) {
657 Log::err(descriptor
, "object type must be Tile");
661 xmlChar
* xStr
= xmlGetProp(node
, BAD_CAST("x"));
662 xmlChar
* yStr
= xmlGetProp(node
, BAD_CAST("y"));
663 // XXX we ignore the object gid... is that okay?
665 // wouldn't have to access tilesets if we had tileDim ourselves
666 long x
= atol((const char*)xStr
) / tilesets
[0].tileDim
.x
;
667 long y
= atol((const char*)yStr
) / tilesets
[0].tileDim
.y
;
668 y
= y
- 1; // bug in tiled? y is 1 too high
670 // We know which Tile is being talked about now... yay
671 Tile
& t
= map
[zpos
][y
][x
];
673 xmlNode
* child
= node
->xmlChildrenNode
; // <properties>
674 child
= child
->xmlChildrenNode
; // <property>
675 for (; child
!= NULL
; child
= child
->next
) {
676 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
677 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
678 if (!xmlStrncmp(name
, BAD_CAST("flags"), 6)) {
679 t
.flags
= splitTileFlags((const char*)value
);
681 else if (!xmlStrncmp(name
, BAD_CAST("onEnter"), 8)) {
684 else if (!xmlStrncmp(name
, BAD_CAST("onLeave"), 8)) {
687 else if (!xmlStrncmp(name
, BAD_CAST("door"), 5)) {
688 t
.door
.reset(parseDoor((const char*)value
));
689 t
.flags
|= npc_nowalk
;
695 unsigned Area::splitTileFlags(const std::string strOfFlags
)
697 std::vector
<std::string
> strs
;
698 strs
= splitStr(strOfFlags
, ",");
700 unsigned flags
= 0x0;
701 BOOST_FOREACH(const std::string
& str
, strs
) {
708 Area::Door
Area::parseDoor(const std::string dest
)
712 Format: destination Area, x, y, z
713 E.g.: "babysfirst.area,1,3,0"
716 std::vector
<std::string
> strs
;
717 strs
= splitStr(dest
, ",");
719 // TODO: verify the validity of the input string... it's coming from
723 door
.coord
.x
= atol(strs
[1].c_str());
724 door
.coord
.y
= atol(strs
[2].c_str());
725 door
.coord
.z
= atol(strs
[3].c_str());