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: TileSet tiles start counting their positions from 0, while layer tiles
25 start counting from 1. I can't imagine why the author did this, but we
26 have to take it into account.
29 Area::Area(Resourcer
* rc
,
32 const std::string
& descriptor
)
33 : rc(rc
), world(world
), player(player
), descriptor(descriptor
),
36 dim
.x
= dim
.y
= dim
.z
= 0;
41 if (musicInst
&& musicInst
->playing())
47 if (!processDescriptor())
51 musicInst
.reset(introMusic
->play(1, 1, false));
55 musicInst
.reset(mainMusic
->play(1, 1, true));
60 void Area::buttonDown(const Gosu::Button btn
)
62 bool attemptingMove
= false;
65 if (btn
== Gosu::kbRight
) {
66 posMove
= coord(1, 0, 0);
67 attemptingMove
= true;
69 else if (btn
== Gosu::kbLeft
) {
70 posMove
= coord(-1, 0, 0);
71 attemptingMove
= true;
73 else if (btn
== Gosu::kbUp
) {
74 posMove
= coord(0, -1, 0);
75 attemptingMove
= true;
77 else if (btn
== Gosu::kbDown
) {
78 posMove
= coord(0, 1, 0);
79 attemptingMove
= true;
83 player
->moveByTile(posMove
);
88 GameWindow
* window
= GameWindow::getWindow();
89 Gosu::Graphics
& graphics
= window
->graphics();
90 const Gosu::Transform trans
= viewportTransform();
91 graphics
.pushTransform(trans
);
93 // Calculate frame to show for each type of tile
94 int millis
= (int)Gosu::milliseconds();
95 BOOST_FOREACH(TileSet
& set
, tilesets
) {
96 BOOST_FOREACH(TileType
& type
, set
.tileTypes
) {
98 int frame
= (millis
% type
.animLen
) /
100 type
.frameShowing
= frame
;
101 type
.graphic
= type
.graphics
[frame
].get();
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
->graphic
;
114 img
->draw(x
*img
->width(), y
*img
->height(), 0);
120 graphics
.popTransform();
123 bool Area::needsRedraw() const
125 if (player
->needsRedraw())
128 // Do any onscreen tile types need to update their animations?
129 int millis
= (int)Gosu::milliseconds();
130 BOOST_FOREACH(const TileSet
& set
, tilesets
) {
131 BOOST_FOREACH(const TileType
& type
, set
.tileTypes
) {
133 int frame
= (millis
% type
.animLen
) /
135 if (frame
!= type
.frameShowing
&&
136 tileTypeOnScreen(type
))
145 void Area::update(unsigned long dt
)
147 if (onIntro
&& !musicInst
->playing()) {
149 musicInst
.reset(mainMusic
->play(1, 1, true));
154 coord_t
Area::getDimensions() const
159 coord_t
Area::getTileDimensions() const
161 return tilesets
[0].tileDim
; // XXX only considers first tileset
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 coord_t
Area::viewportOffset() const
176 GameWindow
* window
= GameWindow::getWindow();
177 Gosu::Graphics
* graphics
= &window
->graphics();
179 double tileWidth
= (double)tilesets
[0].tileDim
.x
;
180 double tileHeight
= (double)tilesets
[0].tileDim
.y
;
181 double windowWidth
= (double)graphics
->width() / tileWidth
;
182 double windowHeight
= (double)graphics
->height() / tileHeight
;
183 double gridWidth
= (double)dim
.x
;
184 double gridHeight
= (double)dim
.y
;
185 double playerX
= (double)player
->getCoordsByPixel().x
/
187 double playerY
= (double)player
->getCoordsByPixel().y
/
191 c
.x
= (long)(center(windowWidth
, gridWidth
, playerX
) * tileWidth
);
192 c
.y
= (long)(center(windowHeight
, gridHeight
, playerY
) * tileHeight
);
198 Gosu::Transform
Area::viewportTransform() const
200 coord_t c
= viewportOffset();
201 Gosu::Transform trans
= Gosu::translate((double)c
.x
, (double)c
.y
);
205 coordcube_t
Area::visibleTiles() const
207 GameWindow
* window
= GameWindow::getWindow();
208 Gosu::Graphics
* graphics
= &window
->graphics();
210 long tileWidth
= tilesets
[0].tileDim
.x
;
211 long tileHeight
= tilesets
[0].tileDim
.y
;
212 int windowWidth
= graphics
->width();
213 int windowHeight
= graphics
->height();
214 coord_t off
= viewportOffset();
217 tiles
.x1
= -off
.x
/ tileWidth
;
218 tiles
.y1
= -off
.y
/ tileHeight
;
221 tiles
.x2
= (long)ceil((double)(windowWidth
- off
.x
) /
223 tiles
.y2
= (long)ceil((double)(windowHeight
- off
.y
) /
229 bool Area::tileTypeOnScreen(const Area::TileType
& search
) const
231 coordcube_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
, "dtd/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
.animated
= false;
371 type
.frameLen
= 1000;
373 type
.graphics
.push_back(set
.tiles
.front());
374 type
.graphic
= type
.graphics
[0].get();
375 set
.tiles
.pop_front();
379 bool Area::processTileType(xmlNode
* node
, TileSet
& ts
)
385 <property name="flags" value="nowalk"/>
386 <property name="onEnter" value="skid();speed(2)"/>
387 <property name="onLeave" value="undo()"/>
392 <property name="animated" value="1"/>
393 <property name="size" value="2"/>
394 <property name="speed" value="2"/>
399 // Initialize a default TileType, we'll build on that.
400 TileType tt
= defaultTileType(ts
);
402 xmlChar
* idstr
= xmlGetProp(node
, BAD_CAST("id"));
403 unsigned id
= (unsigned)atoi((const char*)idstr
); // atoi
404 long expectedId
= ts
.tileTypes
.size();
405 if (id
!= expectedId
) {
406 Log::err(descriptor
, std::string("expected TileType id ") +
407 itostr(expectedId
) + ", but got " +
412 xmlNode
* child
= node
->xmlChildrenNode
; // <properties>
413 child
= child
->xmlChildrenNode
; // <property>
414 for (; child
!= NULL
; child
= child
->next
) {
415 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
416 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
417 if (!xmlStrncmp(name
, BAD_CAST("flags"), 6)) {
418 tt
.flags
= splitTileFlags((const char*)value
);
420 else if (!xmlStrncmp(name
, BAD_CAST("onEnter"), 8)) {
423 else if (!xmlStrncmp(name
, BAD_CAST("onLeave"), 8)) {
426 else if (!xmlStrncmp(name
, BAD_CAST("animated"), 9)) {
427 tt
.animated
= parseBool((const char*)value
);
429 else if (!xmlStrncmp(name
, BAD_CAST("size"), 5)) {
430 int size
= atoi((const char*)value
); // atoi
432 // Add size-1 more frames to our animation.
433 // We already have one from defaultTileType.
434 for (int i
= 1; i
< size
; i
++) {
435 if (ts
.tiles
.empty()) {
436 Log::err(descriptor
, "ran out of tiles"
437 "/frames for animated tile");
440 tt
.graphics
.push_back(ts
.tiles
.front());
441 ts
.tiles
.pop_front();
443 tt
.animLen
= tt
.frameLen
* (int)tt
.graphics
.size();
445 else if (!xmlStrncmp(name
, BAD_CAST("speed"), 6)) {
446 tt
.frameLen
= (int)(1000.0/atof((const char*)value
));
447 tt
.animLen
= tt
.frameLen
* (int)tt
.graphics
.size();
451 ts
.tileTypes
.push_back(tt
);
455 bool Area::processLayer(xmlNode
* node
)
459 <layer name="Tiles0" width="5" height="5">
475 xmlChar
* width
= xmlGetProp(node
, BAD_CAST("width"));
476 xmlChar
* height
= xmlGetProp(node
, BAD_CAST("height"));
477 int x
= atoi((const char*)width
);
478 int y
= atoi((const char*)height
);
480 if (dim
.x
!= x
|| dim
.y
!= y
) {
481 // XXX we need to know the Area we're loading...
482 Log::err(descriptor
, "layer x,y size != map x,y size");
486 xmlNode
* child
= node
->xmlChildrenNode
;
487 for (; child
!= NULL
; child
= child
->next
) {
488 if (!xmlStrncmp(child
->name
, BAD_CAST("properties"), 11)) {
489 if (!processLayerProperties(child
))
492 else if (!xmlStrncmp(child
->name
, BAD_CAST("data"), 5)) {
493 if (!processLayerData(child
))
500 bool Area::processLayerProperties(xmlNode
* node
)
505 <property name="layer" value="0"/>
509 xmlNode
* child
= node
->xmlChildrenNode
;
510 for (; child
!= NULL
; child
= child
->next
) {
511 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
512 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
513 if (!xmlStrncmp(name
, BAD_CAST("layer"), 6)) {
514 int depth
= atoi((const char*)value
);
515 if (depth
!= dim
.z
) {
516 Log::err(descriptor
, "invalid layer depth");
525 bool Area::processLayerData(xmlNode
* node
)
546 xmlNode
* child
= node
->xmlChildrenNode
;
547 for (int i
= 1; child
!= NULL
; i
++, child
= child
->next
) {
548 if (!xmlStrncmp(child
->name
, BAD_CAST("tile"), 5)) {
549 xmlChar
* gidStr
= xmlGetProp(child
, BAD_CAST("gid"));
550 unsigned gid
= (unsigned)atoi((const char*)gidStr
)-1;
552 // XXX can only access first tileset
553 TileType
* type
= &tilesets
[0].tileTypes
[gid
];
558 type
->allOfType
.push_back(&t
);
560 if (row
.size() % dim
.x
== 0) {
573 bool Area::processObjectGroup(xmlNode
* node
)
577 <objectgroup name="Prop0" width="5" height="5">
579 <property name="layer" value="0"/>
581 <object name="tile2" type="Tile" gid="7" x="64" y="320">
583 <property name="onEnter" value="speed(0.5)"/>
584 <property name="onLeave" value="undo()"/>
585 <property name="door" value="grassfield.area,1,1,0"/>
586 <property name="flags" value="npc_nowalk"/>
592 xmlChar
* width
= xmlGetProp(node
, BAD_CAST("width"));
593 xmlChar
* height
= xmlGetProp(node
, BAD_CAST("height"));
594 int x
= atoi((const char*)width
);
595 int y
= atoi((const char*)height
);
599 if (dim
.x
!= x
|| dim
.y
!= y
) {
600 // XXX we need to know the Area we're loading...
602 "objectgroup x,y size != map x,y size");
606 xmlNode
* child
= node
->xmlChildrenNode
;
607 for (; child
!= NULL
; child
= child
->next
) {
608 if (!xmlStrncmp(child
->name
, BAD_CAST("properties"), 11)) {
609 if (!processObjectGroupProperties(child
, &zpos
))
612 else if (!xmlStrncmp(child
->name
, BAD_CAST("object"), 7)) {
613 if (zpos
== -1 || !processObject(child
, zpos
))
621 bool Area::processObjectGroupProperties(xmlNode
* node
, int* zpos
)
626 <property name="layer" value="0"/>
630 xmlNode
* child
= node
->xmlChildrenNode
;
631 for (; child
!= NULL
; child
= child
->next
) {
632 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
633 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
634 if (!xmlStrncmp(name
, BAD_CAST("layer"), 6)) {
635 int layer
= atoi((const char*)value
);
636 if (0 < layer
|| layer
>= (int)dim
.z
) {
637 // XXX we need to know the Area we're loading...
639 "objectgroup must correspond with layer"
649 bool Area::processObject(xmlNode
* node
, int zpos
)
653 <object name="tile2" type="Tile" gid="7" x="64" y="320">
655 <property name="onEnter" value="speed(0.5)"/>
656 <property name="onLeave" value="undo()"/>
657 <property name="door" value="grassfield.area,1,1,0"/>
658 <property name="flags" value="npc_nowalk"/>
663 xmlChar
* type
= xmlGetProp(node
, BAD_CAST("type"));
664 if (xmlStrncmp(type
, BAD_CAST("Tile"), 5)) {
665 Log::err(descriptor
, "object type must be Tile");
669 xmlChar
* xStr
= xmlGetProp(node
, BAD_CAST("x"));
670 xmlChar
* yStr
= xmlGetProp(node
, BAD_CAST("y"));
671 // XXX we ignore the object gid... is that okay?
673 // wouldn't have to access tilesets if we had tileDim ourselves
674 long x
= atol((const char*)xStr
) / tilesets
[0].tileDim
.x
;
675 long y
= atol((const char*)yStr
) / tilesets
[0].tileDim
.y
;
676 y
= y
- 1; // bug in tiled? y is 1 too high
678 // We know which Tile is being talked about now... yay
679 Tile
& t
= map
[zpos
][y
][x
];
681 xmlNode
* child
= node
->xmlChildrenNode
; // <properties>
682 child
= child
->xmlChildrenNode
; // <property>
683 for (; child
!= NULL
; child
= child
->next
) {
684 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
685 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
686 if (!xmlStrncmp(name
, BAD_CAST("flags"), 6)) {
687 t
.flags
= splitTileFlags((const char*)value
);
689 else if (!xmlStrncmp(name
, BAD_CAST("onEnter"), 8)) {
692 else if (!xmlStrncmp(name
, BAD_CAST("onLeave"), 8)) {
695 else if (!xmlStrncmp(name
, BAD_CAST("door"), 5)) {
696 t
.door
.reset(parseDoor((const char*)value
));
697 t
.flags
|= npc_nowalk
;
703 unsigned Area::splitTileFlags(const std::string strOfFlags
)
705 std::vector
<std::string
> strs
;
706 strs
= splitStr(strOfFlags
, ",");
708 unsigned flags
= 0x0;
709 BOOST_FOREACH(const std::string
& str
, strs
)
711 // TODO: reimplement comparisons as a hash table
712 if (str
== "nowalk") {
719 Area::Door
Area::parseDoor(const std::string dest
)
723 Format: destination Area, x, y, z
724 E.g.: "babysfirst.area,1,3,0"
727 std::vector
<std::string
> strs
;
728 strs
= splitStr(dest
, ",");
730 // TODO: verify the validity of the input string... it's coming from
734 door
.coord
.x
= atol(strs
[1].c_str());
735 door
.coord
.y
= atol(strs
[2].c_str());
736 door
.coord
.z
= atol(strs
[3].c_str());