1 /*********************************
2 ** Tsunagari Tile Engine **
4 ** Copyright 2011-2012 OmegaSDG **
5 *********************************/
9 #include <stdlib.h> // for exit(1) on fatal
12 #include <boost/foreach.hpp>
13 #include <boost/format.hpp>
14 #include <boost/shared_ptr.hpp>
15 #include <Gosu/Graphics.hpp>
16 #include <Gosu/Image.hpp>
17 #include <Gosu/Math.hpp>
18 #include <Gosu/Timing.hpp>
25 #include "resourcer.h"
30 #define ASSERT(x) if (!(x)) return false
32 /* NOTE: In the TMX map format used by Tiled, tileset tiles start counting
33 their Y-positions from 0, while layer tiles start counting from 1. I
34 can't imagine why the author did this, but we have to take it into
39 static T
wrap(T min
, T value
, T max
)
46 Area::Area(Viewport
* view
,
49 const std::string
& descriptor
)
53 colorOverlay(0, 0, 0, 0),
56 loopX(false), loopY(false),
59 descriptor(descriptor
)
80 music
->setIntro(musicIntro
);
81 music
->setLoop(musicLoop
);
83 pythonSetGlobal("Area", this);
87 void Area::buttonDown(const Gosu::Button btn
)
89 if (btn
== Gosu::kbRight
)
90 player
->startMovement(ivec2(1, 0));
91 else if (btn
== Gosu::kbLeft
)
92 player
->startMovement(ivec2(-1, 0));
93 else if (btn
== Gosu::kbUp
)
94 player
->startMovement(ivec2(0, -1));
95 else if (btn
== Gosu::kbDown
)
96 player
->startMovement(ivec2(0, 1));
97 else if (btn
== Gosu::kbSpace
)
101 void Area::buttonUp(const Gosu::Button btn
)
103 if (btn
== Gosu::kbRight
)
104 player
->stopMovement(ivec2(1, 0));
105 else if (btn
== Gosu::kbLeft
)
106 player
->stopMovement(ivec2(-1, 0));
107 else if (btn
== Gosu::kbUp
)
108 player
->stopMovement(ivec2(0, -1));
109 else if (btn
== Gosu::kbDown
)
110 player
->stopMovement(ivec2(0, 1));
115 updateTileAnimations();
122 bool Area::needsRedraw() const
126 if (player
->needsRedraw())
129 BOOST_FOREACH(Entity
* e
, entities
)
130 if (e
->needsRedraw())
133 // Do any on-screen tile types need to update their animations?
134 const icube tiles
= visibleTiles();
135 for (int z
= tiles
.z1
; z
< tiles
.z2
; z
++) {
136 for (int y
= tiles
.y1
; y
< tiles
.y2
; y
++) {
137 for (int x
= tiles
.x1
; x
< tiles
.x2
; x
++) {
138 const Tile
* tile
= getTile(x
, y
, z
);
139 const TileType
* type
= tile
->getType();
140 if (type
&& type
->needsRedraw())
148 void Area::requestRedraw()
153 void Area::update(unsigned long dt
)
155 pythonSetGlobal("Area", this);
156 updateScript
.invoke();
158 pythonSetGlobal("Area", this);
161 BOOST_FOREACH(Entity
* e
, entities
)
168 void Area::setColorOverlay(int r
, int g
, int b
, int a
)
170 using namespace Gosu
;
172 if (0 <= r
&& r
< 256 &&
176 Color::Channel ac
= (Color::Channel
)a
;
177 Color::Channel rc
= (Color::Channel
)r
;
178 Color::Channel gc
= (Color::Channel
)g
;
179 Color::Channel bc
= (Color::Channel
)b
;
180 colorOverlay
= Color(ac
, rc
, gc
, bc
);
184 PyErr_Format(PyExc_ValueError
,
185 "Area::color_overlay() arguments must be "
186 "between 0 and 255");
192 const Tile
* Area::getTile(int x
, int y
, int z
) const
195 x
= wrap(0, x
, dim
.x
);
197 y
= wrap(0, y
, dim
.y
);
198 if (inBounds(x
, y
, z
))
199 return &map
[z
][y
][x
];
204 const Tile
* Area::getTile(int x
, int y
, double z
) const
206 return getTile(x
, y
, depthIndex(z
));
209 const Tile
* Area::getTile(icoord phys
) const
211 return getTile(phys
.x
, phys
.y
, phys
.z
);
214 const Tile
* Area::getTile(vicoord virt
) const
216 return getTile(virt2phys(virt
));
219 const Tile
* Area::getTile(rcoord virt
) const
221 return getTile(virt2phys(virt
));
224 Tile
* Area::getTile(int x
, int y
, int z
)
227 x
= wrap(0, x
, dim
.x
);
229 y
= wrap(0, y
, dim
.y
);
230 if (inBounds(x
, y
, z
))
231 return &map
[z
][y
][x
];
236 Tile
* Area::getTile(int x
, int y
, double z
)
238 return getTile(x
, y
, depthIndex(z
));
241 Tile
* Area::getTile(icoord phys
)
243 return getTile(phys
.x
, phys
.y
, phys
.z
);
246 Tile
* Area::getTile(vicoord virt
)
248 return getTile(virt2phys(virt
));
251 Tile
* Area::getTile(rcoord virt
)
253 return getTile(virt2phys(virt
));
256 TileSet
* Area::getTileSet(const std::string
& imagePath
)
258 std::map
<std::string
, TileSet
>::iterator it
;
259 it
= tileSets
.find(imagePath
);
260 if (it
== tileSets
.end()) {
261 Log::err("Area", "tileset " + imagePath
+ " not found");
264 return &tileSets
[imagePath
];
268 ivec3
Area::getDimensions() const
273 ivec2
Area::getTileDimensions() const
278 icube
Area::visibleTileBounds() const
280 rvec2 screen
= view
->getVirtRes();
281 rvec2 off
= view
->getMapOffset();
283 int x1
= (int)floor(off
.x
/ tileDim
.x
);
284 int y1
= (int)floor(off
.y
/ tileDim
.y
);
285 int x2
= (int)ceil((screen
.x
+ off
.x
) / tileDim
.x
);
286 int y2
= (int)ceil((screen
.y
+ off
.y
) / tileDim
.y
);
288 return icube(x1
, y1
, 0, x2
, y2
, dim
.z
);
291 icube
Area::visibleTiles() const
293 icube cube
= visibleTileBounds();
295 cube
.x1
= std::max(cube
.x1
, 0);
296 cube
.x2
= std::min(cube
.x2
, dim
.x
);
299 cube
.y1
= std::max(cube
.y1
, 0);
300 cube
.y2
= std::min(cube
.y2
, dim
.y
);
305 bool Area::inBounds(int x
, int y
, int z
) const
307 return ((loopX
|| (0 <= x
&& x
< dim
.x
)) &&
308 (loopY
|| (0 <= y
&& y
< dim
.y
)) &&
309 0 <= z
&& z
< dim
.z
);
312 bool Area::inBounds(int x
, int y
, double z
) const
314 return inBounds(x
, y
, depthIndex(z
));
317 bool Area::inBounds(icoord phys
) const
319 return inBounds(phys
.x
, phys
.y
, phys
.z
);
322 bool Area::inBounds(vicoord virt
) const
324 return inBounds(virt2phys(virt
));
327 bool Area::inBounds(rcoord virt
) const
329 return inBounds(virt2phys(virt
));
332 bool Area::inBounds(Entity
* ent
) const
334 return inBounds(ent
->getPixelCoord());
339 bool Area::loopsInX() const
344 bool Area::loopsInY() const
349 const std::string
Area::getDescriptor() const
354 Entity
* Area::spawnEntity(const std::string
& descriptor
,
355 int x
, int y
, double z
, const std::string
& phase
)
357 Entity
* e
= new NPC();
358 if (!e
->init(descriptor
)) {
364 if (!e
->setPhase(phase
)) {
369 if (!inBounds(x
, y
, z
)) {
370 Log::err(descriptor
, boost::str(
371 boost::format("not in map bounds: x:%d y:%d z:%f")
377 e
->setTileCoords(x
, y
, z
);
382 void Area::insert(Entity
* e
)
387 void Area::erase(Entity
* e
)
394 vicoord
Area::phys2virt_vi(icoord phys
) const
396 return vicoord(phys
.x
, phys
.y
, indexDepth(phys
.z
));
399 rcoord
Area::phys2virt_r(icoord phys
) const
402 (double)phys
.x
* tileDim
.x
,
403 (double)phys
.y
* tileDim
.y
,
408 icoord
Area::virt2phys(vicoord virt
) const
410 return icoord(virt
.x
, virt
.y
, depthIndex(virt
.z
));
413 icoord
Area::virt2phys(rcoord virt
) const
416 (int)(virt
.x
/ tileDim
.x
),
417 (int)(virt
.y
/ tileDim
.y
),
422 rcoord
Area::virt2virt(vicoord virt
) const
425 (double)virt
.x
* tileDim
.x
,
426 (double)virt
.y
* tileDim
.y
,
431 vicoord
Area::virt2virt(rcoord virt
) const
434 (int)virt
.x
/ tileDim
.x
,
435 (int)virt
.y
/ tileDim
.y
,
441 int Area::depthIndex(double depth
) const
443 return depth2idx
.find(depth
)->second
;
446 double Area::indexDepth(int idx
) const
448 return idx2depth
[idx
];
453 void Area::runLoadScripts()
455 World
* world
= World::instance();
456 world
->runAreaLoadScript(this);
458 pythonSetGlobal("Area", this);
462 void Area::updateTileAnimations()
464 const int millis
= GameWindow::instance().time();
465 BOOST_FOREACH(tilesets_t::value_type
& pair
, tileSets
) {
466 TileSet
& set
= pair
.second
;
467 int w
= set
.getWidth();
468 int h
= set
.getHeight();
469 for (int y
= 0; y
< h
; y
++) {
470 for (int x
= 0; x
< w
; x
++) {
471 TileType
* type
= set
.get(x
, y
);
472 type
->anim
.updateFrame(millis
);
478 void Area::drawTiles() const
480 const icube tiles
= visibleTiles();
481 for (int z
= tiles
.z1
; z
< tiles
.z2
; z
++) {
482 double depth
= idx2depth
[z
];
483 for (int y
= tiles
.y1
; y
< tiles
.y2
; y
++) {
484 for (int x
= tiles
.x1
; x
< tiles
.x2
; x
++) {
485 const Tile
* tile
= getTile(x
, y
, z
);
486 drawTile(*tile
, x
, y
, depth
);
492 void Area::drawTile(const Tile
& tile
, int x
, int y
, double depth
) const
494 const TileType
* type
= (TileType
*)tile
.parent
;
496 const Gosu::Image
* img
= type
->anim
.frame();
498 img
->draw((double)x
*img
->width(),
499 (double)y
*img
->height(), depth
);
503 void Area::drawEntities()
505 BOOST_FOREACH(Entity
* e
, entities
) {
511 void Area::drawColorOverlay()
513 if (colorOverlay
.alpha() != 0) {
514 GameWindow
& window
= GameWindow::instance();
515 Gosu::Color c
= colorOverlay
;
516 int x
= window
.width();
517 int y
= window
.height();
518 window
.graphics().drawQuad(
528 boost::python::tuple
Area::pyGetDimensions()
530 using namespace boost::python
;
533 BOOST_FOREACH(double dep
, idx2depth
)
535 return make_tuple(dim
.x
, dim
.y
, zs
);
540 using namespace boost::python
;
542 class_
<Area
>("Area", no_init
)
543 .add_property("descriptor", &Area::getDescriptor
)
544 .add_property("dimensions", &Area::pyGetDimensions
)
545 .def("redraw", &Area::requestRedraw
)
546 .def("tileset", &Area::getTileSet
,
547 return_value_policy
<reference_existing_object
>())
549 static_cast<Tile
* (Area::*) (int, int, double)>
551 return_value_policy
<reference_existing_object
>())
553 static_cast<bool (Area::*) (int, int, double) const>
555 .def("color_overlay", &Area::setColorOverlay
)
556 .def("new_entity", &Area::spawnEntity
,
557 return_value_policy
<reference_existing_object
>())
558 .def_readwrite("on_focus", &Area::focusScript
)
559 .def_readwrite("on_update", &Area::updateScript
)