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
, World
* world
, Entity
* player
, const std::string
& descriptor
)
25 : rc(rc
), world(world
), player(player
), descriptor(descriptor
)
36 if (!processDescriptor()) // Try to load in descriptor.
41 void Area::buttonDown(const Gosu::Button btn
)
43 bool attemptingMove
= false;
46 if (btn
== Gosu::kbRight
) {
47 posMove
= coord(1, 0, 0);
48 attemptingMove
= true;
50 else if (btn
== Gosu::kbLeft
) {
51 posMove
= coord(-1, 0, 0);
52 attemptingMove
= true;
54 else if (btn
== Gosu::kbUp
) {
55 posMove
= coord(0, -1, 0);
56 attemptingMove
= true;
58 else if (btn
== Gosu::kbDown
) {
59 posMove
= coord(0, 1, 0);
60 attemptingMove
= true;
64 coord_t newCoord
= player
->getCoordsByTile();
65 newCoord
.x
+= posMove
.x
;
66 newCoord
.y
+= posMove
.y
;
67 newCoord
.z
+= posMove
.z
;
68 Tile
* dest
= getTile(newCoord
);
69 if ((dest
->flags
& player_nowalk
) != 0 ||
70 (dest
->type
->flags
& player_nowalk
) != 0) {
71 // The tile we're trying to move onto is set as nowalk
72 // for the player. Stop here.
76 world
->loadArea(dest
->door
->area
, dest
->door
->coord
);
79 player
->moveByTile(posMove
);
86 GameWindow
* window
= GameWindow::getWindow();
87 Gosu::Graphics
* graphics
= &window
->graphics();
88 Gosu::Transform trans
= translateCoords();
89 graphics
->pushTransform(trans
);
91 for (unsigned int layer
= 0; layer
!= map
.size(); layer
++)
93 grid_t grid
= map
[layer
];
94 for (unsigned int y
= 0; y
!= grid
.size(); y
++)
97 for (unsigned int x
= 0; x
!= row
.size(); x
++)
99 // TODO support animations
101 Gosu::Image
* img
= tile
->type
->graphics
[0];
102 img
->draw(x
*img
->width(), y
*img
->height(), 0);
108 graphics
->popTransform();
111 static double bound(double low
, double x
, double high
)
120 static double center(double w
, double g
, double p
)
122 return w
>g
? (w
-g
)/2.0 : bound(w
-g
, w
/2.0-p
, 0);
125 Gosu::Transform
Area::translateCoords()
127 GameWindow
* window
= GameWindow::getWindow();
128 Gosu::Graphics
* graphics
= &window
->graphics();
131 double tileSize
= map
[0][0][0]->type
->graphics
[0]->width();
132 double windowWidth
= graphics
->width() / tileSize
;
133 double windowHeight
= graphics
->height() / tileSize
;
134 double gridWidth
= dim
.x
;
135 double gridHeight
= dim
.y
;
136 double playerX
= player
->getCoordsByPixel().x
/ tileSize
+ 0.5;
137 double playerY
= player
->getCoordsByPixel().y
/ tileSize
+ 0.5;
140 c
.x
= center(windowWidth
, gridWidth
, playerX
) * tileSize
;
141 c
.y
= center(windowHeight
, gridHeight
, playerY
) * tileSize
;
143 Gosu::Transform trans
= Gosu::translate(c
.x
, c
.y
);
147 bool Area::needsRedraw() const
149 return player
->needsRedraw();
152 bool Area::processDescriptor()
154 xmlDoc
* doc
= rc
->getXMLDoc(descriptor
);
158 // use RAII to ensure doc is freed
159 boost::shared_ptr
<void> alwaysFreeTheDoc(doc
, xmlFreeDoc
);
161 // Iterate and process children of <map>
162 xmlNode
* root
= xmlDocGetRootElement(doc
); // <map> element
164 xmlChar
* width
= xmlGetProp(root
, BAD_CAST("width"));
165 xmlChar
* height
= xmlGetProp(root
, BAD_CAST("height"));
166 dim
.x
= atol((const char*)width
);
167 dim
.y
= atol((const char*)height
);
169 xmlNode
* child
= root
->xmlChildrenNode
;
170 for (; child
!= NULL
; child
= child
->next
) {
171 if (!xmlStrncmp(child
->name
, BAD_CAST("properties"), 11)) {
172 if (!processMapProperties(child
))
175 else if (!xmlStrncmp(child
->name
, BAD_CAST("tileset"), 8)) {
176 if (!processTileset(child
))
179 else if (!xmlStrncmp(child
->name
, BAD_CAST("layer"), 6)) {
180 if (!processLayer(child
))
183 else if (!xmlStrncmp(child
->name
, BAD_CAST("objectgroup"), 12)) {
184 if (!processObjectGroup(child
))
192 bool Area::processMapProperties(xmlNode
* node
)
197 <property name="areaspec" value="1"/>
198 <property name="author" value="Michael D. Reiley"/>
199 <property name="name" value="Baby's First Area"/>
200 <property name="music_loop" value="true"/>
201 <property name="music_main" value="wind.music"/>
202 <property name="onLoad" value="babysfirst_init()"/>
203 <property name="scripts" value="areainits.event,test.event"/>
207 xmlNode
* child
= node
->xmlChildrenNode
;
208 for (; child
!= NULL
; child
= child
->next
) {
209 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
210 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
211 if (!xmlStrncmp(name
, BAD_CAST("author"), 7))
212 author
= (const char*)value
;
213 else if (!xmlStrncmp(name
, BAD_CAST("name"), 5))
214 this->name
= (const char*)value
;
215 else if (!xmlStrncmp(name
, BAD_CAST("music_loop"), 11))
216 main
.loop
= parseBool((const char*)value
);
217 else if (!xmlStrncmp(name
, BAD_CAST("music_main"), 11))
218 main
.filename
= (const char*)value
;
219 else if (!xmlStrncmp(name
, BAD_CAST("onLoad"), 7))
220 onLoadEvents
= (const char*)value
;
221 else if (!xmlStrncmp(name
, BAD_CAST("scripts"), 8))
222 scripts
= (const char*)value
; // TODO split(), load
227 bool Area::processTileset(xmlNode
* node
)
231 <tileset firstgid="1" name="tiles.sheet" tilewidth="64" tileheight="64">
232 <image source="tiles.sheet" width="256" height="256"/>
239 xmlChar
* width
= xmlGetProp(node
, BAD_CAST("tilewidth"));
240 xmlChar
* height
= xmlGetProp(node
, BAD_CAST("tileheight"));
241 ts
.tiledim
.x
= atol((const char*)width
);
242 ts
.tiledim
.y
= atol((const char*)height
);
244 xmlNode
* child
= node
->xmlChildrenNode
;
245 for (; child
!= NULL
; child
= child
->next
) {
246 if (!xmlStrncmp(child
->name
, BAD_CAST("tile"), 5)) {
247 xmlChar
* idstr
= xmlGetProp(child
, BAD_CAST("id"));
248 unsigned id
= atol((const char*)idstr
);
250 // Undeclared TileTypes have default properties.
251 while (ts
.defaults
.size() != id
) {
252 TileType tt
= defaultTileType(ts
.source
,
253 ts
.tiledim
, ts
.defaults
.size());
254 ts
.defaults
.push_back(tt
);
257 // Handle explicit TileType
258 if (!processTileType(child
, ts
))
261 else if (!xmlStrncmp(child
->name
, BAD_CAST("image"), 6)) {
262 xmlChar
* source
= xmlGetProp(child
, BAD_CAST("source"));
263 ts
.source
= rc
->getBitmap((const char*)source
);
267 // Generate default tile types in range (m,n] where m is the last
268 // explicitly declared type and n is the number we require.
269 unsigned srcsz
= ts
.source
.width() * ts
.source
.height();
270 unsigned tilesz
= ts
.tiledim
.x
* ts
.tiledim
.y
;
271 while (ts
.defaults
.size() != srcsz
/ tilesz
) {
272 TileType tt
= defaultTileType(ts
.source
,
273 ts
.tiledim
, ts
.defaults
.size());
274 ts
.defaults
.push_back(tt
);
277 tilesets
.push_back(ts
);
281 Area::TileType
Area::defaultTileType(const Gosu::Bitmap source
, coord_t tiledim
,
284 int x
= (tiledim
.x
* id
) % source
.width();
285 int y
= (tiledim
.y
* id
) / source
.height() * tiledim
.y
; // ???
288 Gosu::Image
* img
= rc
->bitmapSection(source
, x
, y
,
289 tiledim
.x
, tiledim
.y
, true);
290 tt
.graphics
.push_back(img
);
297 bool Area::processTileType(xmlNode
* node
, Tileset
& ts
)
303 <property name="flags" value="nowalk"/>
304 <property name="onEnter" value="skid();speed(2)"/>
305 <property name="onLeave" value="undo()"/>
310 <property name="animated" value="1"/>
311 <property name="size" value="2"/>
312 <property name="speed" value="2"/>
317 // Initialize a default TileType, we'll build on that.
318 TileType tt
= defaultTileType(ts
.source
,
319 ts
.tiledim
, ts
.defaults
.size());
321 xmlChar
* idstr
= xmlGetProp(node
, BAD_CAST("id"));
322 unsigned id
= atol((const char*)idstr
);
323 if (id
!= ts
.defaults
.size()) {
324 // XXX we need to know the Area we're loading...
325 Log::err("unknown area", std::string("expected TileType id ") +
326 itostr(ts
.defaults
.size()) + ", but got " + itostr(id
));
330 xmlNode
* child
= node
->xmlChildrenNode
; // <properties>
331 child
= child
->xmlChildrenNode
; // <property>
332 for (; child
!= NULL
; child
= child
->next
) {
333 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
334 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
335 if (!xmlStrncmp(name
, BAD_CAST("flags"), 6)) {
336 tt
.flags
= splitTileFlags((const char*)value
);
338 else if (!xmlStrncmp(name
, BAD_CAST("onEnter"), 8)) {
341 else if (!xmlStrncmp(name
, BAD_CAST("onLeave"), 8)) {
344 else if (!xmlStrncmp(name
, BAD_CAST("animated"), 9)) {
345 tt
.animated
= parseBool((const char*)value
);
347 else if (!xmlStrncmp(name
, BAD_CAST("size"), 5)) {
350 else if (!xmlStrncmp(name
, BAD_CAST("speed"), 6)) {
351 tt
.ani_speed
= atol((const char*)value
);
355 ts
.defaults
.push_back(tt
);
359 bool Area::processLayer(xmlNode
* node
)
363 <layer name="Tiles0" width="5" height="5">
379 xmlChar
* width
= xmlGetProp(node
, BAD_CAST("width"));
380 xmlChar
* height
= xmlGetProp(node
, BAD_CAST("height"));
381 int x
= atol((const char*)width
);
382 int y
= atol((const char*)height
);
384 if (dim
.x
!= x
|| dim
.y
!= y
) {
385 // XXX we need to know the Area we're loading...
386 Log::err("unknown area", "layer x,y size != map x,y size");
390 xmlNode
* child
= node
->xmlChildrenNode
;
391 for (; child
!= NULL
; child
= child
->next
) {
392 if (!xmlStrncmp(child
->name
, BAD_CAST("properties"), 11)) {
393 if (!processLayerProperties(child
))
396 else if (!xmlStrncmp(child
->name
, BAD_CAST("data"), 5)) {
397 if (!processLayerData(child
))
404 bool Area::processLayerProperties(xmlNode
* node
)
409 <property name="layer" value="0"/>
413 xmlNode
* child
= node
->xmlChildrenNode
;
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("layer"), 6)) {
418 int depth
= atol((const char*)value
);
419 if (depth
!= dim
.z
) {
420 Log::err("unknown area", "invalid layer depth");
429 bool Area::processLayerData(xmlNode
* node
)
447 xmlNode
* child
= node
->xmlChildrenNode
;
448 for (int i
= 1; child
!= NULL
; i
++, child
= child
->next
) {
449 if (!xmlStrncmp(child
->name
, BAD_CAST("tile"), 5)) {
450 xmlChar
* gidStr
= xmlGetProp(child
, BAD_CAST("gid"));
451 unsigned gid
= atol((const char*)gidStr
)-1;
453 t
->type
= &tilesets
[0].defaults
[gid
]; // XXX can only access first tileset
457 if (row
.size() % dim
.x
== 0) {
469 bool Area::processObjectGroup(xmlNode
* node
)
473 <objectgroup name="Prop0" width="5" height="5">
475 <property name="layer" value="0"/>
477 <object name="tile2" type="Tile" gid="7" x="64" y="320">
479 <property name="onEnter" value="speed(0.5)"/>
480 <property name="onLeave" value="undo()"/>
481 <property name="door" value="grassfield.area,1,1,0"/>
482 <property name="flags" value="npc_nowalk"/>
488 xmlChar
* width
= xmlGetProp(node
, BAD_CAST("width"));
489 xmlChar
* height
= xmlGetProp(node
, BAD_CAST("height"));
490 int x
= atol((const char*)width
);
491 int y
= atol((const char*)height
);
495 if (dim
.x
!= x
|| dim
.y
!= y
) {
496 // XXX we need to know the Area we're loading...
497 Log::err("unknown area", "objectgroup x,y size != map x,y size");
501 xmlNode
* child
= node
->xmlChildrenNode
;
502 for (; child
!= NULL
; child
= child
->next
) {
503 if (!xmlStrncmp(child
->name
, BAD_CAST("properties"), 11)) {
504 if (!processObjectGroupProperties(child
, &zpos
))
507 else if (!xmlStrncmp(child
->name
, BAD_CAST("object"), 7)) {
508 if (zpos
== -1 || !processObject(child
, zpos
))
516 bool Area::processObjectGroupProperties(xmlNode
* node
, int* zpos
)
521 <property name="layer" value="0"/>
525 xmlNode
* child
= node
->xmlChildrenNode
;
526 for (; child
!= NULL
; child
= child
->next
) {
527 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
528 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
529 if (!xmlStrncmp(name
, BAD_CAST("layer"), 6)) {
530 int layer
= atol((const char*)value
);
531 if (0 < layer
|| layer
>= (int)dim
.z
) {
532 // XXX we need to know the Area we're loading...
533 Log::err("unknown area",
534 "objectgroup must correspond with layer"
544 bool Area::processObject(xmlNode
* node
, int zpos
)
548 <object name="tile2" type="Tile" gid="7" x="64" y="320">
550 <property name="onEnter" value="speed(0.5)"/>
551 <property name="onLeave" value="undo()"/>
552 <property name="door" value="grassfield.area,1,1,0"/>
553 <property name="flags" value="npc_nowalk"/>
558 xmlChar
* type
= xmlGetProp(node
, BAD_CAST("type"));
559 if (xmlStrncmp(type
, BAD_CAST("Tile"), 5)) {
560 Log::err("unknown area", "object type must be Tile");
564 xmlChar
* xStr
= xmlGetProp(node
, BAD_CAST("x"));
565 xmlChar
* yStr
= xmlGetProp(node
, BAD_CAST("y"));
566 // XXX we ignore the object gid... is that okay?
568 // wouldn't have to access tilesets if we had tiledim ourselves
569 int x
= atol((const char*)xStr
) / tilesets
[0].tiledim
.x
;
570 int y
= atol((const char*)yStr
) / tilesets
[0].tiledim
.y
;
571 y
= y
- 1; // bug in tiled? y is 1 too high
573 // We know which Tile is being talked about now... yay
574 Tile
* t
= map
[zpos
][y
][x
];
576 xmlNode
* child
= node
->xmlChildrenNode
; // <properties>
577 child
= child
->xmlChildrenNode
; // <property>
578 for (; child
!= NULL
; child
= child
->next
) {
579 xmlChar
* name
= xmlGetProp(child
, BAD_CAST("name"));
580 xmlChar
* value
= xmlGetProp(child
, BAD_CAST("value"));
581 if (!xmlStrncmp(name
, BAD_CAST("flags"), 6)) {
582 t
->flags
= splitTileFlags((const char*)value
);
584 else if (!xmlStrncmp(name
, BAD_CAST("onEnter"), 8)) {
587 else if (!xmlStrncmp(name
, BAD_CAST("onLeave"), 8)) {
590 else if (!xmlStrncmp(name
, BAD_CAST("door"), 5)) {
591 t
->door
= parseDoor((const char*)value
);
597 unsigned Area::splitTileFlags(const std::string strOfFlags
)
599 std::vector
<std::string
> strs
;
600 strs
= splitStr(strOfFlags
, ",");
602 unsigned flags
= 0x0;
603 BOOST_FOREACH(std::string str
, strs
)
605 // TODO: reimplement comparisons as a hash table
606 if (str
== "nowalk") {
613 Area::Door
* Area::parseDoor(const std::string dest
)
615 std::vector
<std::string
> strs
;
616 strs
= splitStr(dest
, ",");
618 // TODO: verify the validity of the input string... it's coming from
620 Door
* door
= new Door
;
621 door
->area
= strs
[0];
622 door
->coord
.x
= atol(strs
[1].c_str());
623 door
->coord
.y
= atol(strs
[2].c_str());
624 door
->coord
.z
= atol(strs
[3].c_str());
628 coord_t
Area::getDimensions() const
633 Area::Tile
* Area::getTile(coord_t c
)
635 return map
[c
.z
][c
.y
][c
.x
];