small cleanup
[Tsunagari.git] / src / area.cpp
blob7690fe12ba113dfc2a15c902a9e8700f3b43f7b7
1 /*********************************
2 ** Tsunagari Tile Engine **
3 ** area.cpp **
4 ** Copyright 2011-2012 OmegaSDG **
5 *********************************/
7 #include <algorithm>
8 #include <math.h>
9 #include <stdlib.h> // for exit(1) on fatal
10 #include <vector>
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>
20 #include "area.h"
21 #include "entity.h"
22 #include "log.h"
23 #include "python.h"
24 #include "resourcer.h"
25 #include "tile.h"
26 #include "window.h"
27 #include "world.h"
29 #define ASSERT(x) if (!(x)) return false
31 /* NOTE: In the TMX map format used by Tiled, tileset tiles start counting
32 their Y-positions from 0, while layer tiles start counting from 1. I
33 can't imagine why the author did this, but we have to take it into
34 account.
37 template<class T>
38 static T wrap(T min, T value, T max)
40 while (value < min)
41 value += max;
42 return value % max;
45 Area::Area(Viewport* view,
46 Player* player,
47 Music* music,
48 const std::string& descriptor)
49 : view(view),
50 player(player),
51 music(music),
52 colorOverlay(0, 0, 0, 0),
53 dim(0, 0, 0),
54 tileDim(0, 0),
55 loopX(false), loopY(false),
56 beenFocused(false),
57 redraw(true),
58 descriptor(descriptor)
62 Area::~Area()
66 bool Area::init()
68 // Abstract method.
69 return false;
72 void Area::focus()
74 if (!beenFocused) {
75 beenFocused = true;
76 runLoadScripts();
79 music->setIntro(musicIntro);
80 music->setLoop(musicLoop);
82 pythonSetGlobal("Area", this);
83 focusScript.invoke();
86 void Area::buttonDown(const Gosu::Button btn)
88 if (btn == Gosu::kbRight)
89 player->startMovement(ivec2(1, 0));
90 else if (btn == Gosu::kbLeft)
91 player->startMovement(ivec2(-1, 0));
92 else if (btn == Gosu::kbUp)
93 player->startMovement(ivec2(0, -1));
94 else if (btn == Gosu::kbDown)
95 player->startMovement(ivec2(0, 1));
96 else if (btn == Gosu::kbSpace)
97 player->useTile();
100 void Area::buttonUp(const Gosu::Button btn)
102 if (btn == Gosu::kbRight)
103 player->stopMovement(ivec2(1, 0));
104 else if (btn == Gosu::kbLeft)
105 player->stopMovement(ivec2(-1, 0));
106 else if (btn == Gosu::kbUp)
107 player->stopMovement(ivec2(0, -1));
108 else if (btn == Gosu::kbDown)
109 player->stopMovement(ivec2(0, 1));
112 void Area::draw()
114 updateTileAnimations();
115 drawTiles();
116 drawEntities();
117 drawColorOverlay();
118 redraw = false;
121 bool Area::needsRedraw() const
123 if (redraw)
124 return true;
125 if (player->needsRedraw())
126 return true;
128 BOOST_FOREACH(Entity* e, entities)
129 if (e->needsRedraw())
130 return true;
132 // Do any on-screen tile types need to update their animations?
133 const icube tiles = visibleTiles();
134 for (int z = tiles.z1; z < tiles.z2; z++) {
135 for (int y = tiles.y1; y < tiles.y2; y++) {
136 for (int x = tiles.x1; x < tiles.x2; x++) {
137 const Tile* tile = getTile(x, y, z);
138 const TileType* type = tile->getType();
139 if (type && type->needsRedraw())
140 return true;
144 return false;
147 void Area::requestRedraw()
149 redraw = true;
152 void Area::update(unsigned long dt)
154 pythonSetGlobal("Area", this);
155 updateScript.invoke();
157 pythonSetGlobal("Area", this);
158 player->update(dt);
160 BOOST_FOREACH(Entity* e, entities)
161 e->update(dt);
163 view->update(dt);
164 music->update();
167 void Area::setColorOverlay(int r, int g, int b, int a)
169 using namespace Gosu;
171 if (0 <= r && r < 256 &&
172 0 <= g && g < 256 &&
173 0 <= b && b < 256 &&
174 0 <= a && a < 256) {
175 Color::Channel ac = (Color::Channel)a;
176 Color::Channel rc = (Color::Channel)r;
177 Color::Channel gc = (Color::Channel)g;
178 Color::Channel bc = (Color::Channel)b;
179 colorOverlay = Color(ac, rc, gc, bc);
180 redraw = true;
182 else {
183 PyErr_Format(PyExc_ValueError,
184 "Area::color_overlay() arguments must be "
185 "between 0 and 255");
191 const Tile* Area::getTile(int x, int y, int z) const
193 if (loopX)
194 x = wrap(0, x, dim.x);
195 if (loopY)
196 y = wrap(0, y, dim.y);
197 if (inBounds(x, y, z))
198 return &map[z][y][x];
199 else
200 return NULL;
203 const Tile* Area::getTile(int x, int y, double z) const
205 return getTile(x, y, depthIndex(z));
208 const Tile* Area::getTile(icoord phys) const
210 return getTile(phys.x, phys.y, phys.z);
213 const Tile* Area::getTile(vicoord virt) const
215 return getTile(virt2phys(virt));
218 const Tile* Area::getTile(rcoord virt) const
220 return getTile(virt2phys(virt));
223 Tile* Area::getTile(int x, int y, int z)
225 if (loopX)
226 x = wrap(0, x, dim.x);
227 if (loopY)
228 y = wrap(0, y, dim.y);
229 if (inBounds(x, y, z))
230 return &map[z][y][x];
231 else
232 return NULL;
235 Tile* Area::getTile(int x, int y, double z)
237 return getTile(x, y, depthIndex(z));
240 Tile* Area::getTile(icoord phys)
242 return getTile(phys.x, phys.y, phys.z);
245 Tile* Area::getTile(vicoord virt)
247 return getTile(virt2phys(virt));
250 Tile* Area::getTile(rcoord virt)
252 return getTile(virt2phys(virt));
255 TileSet* Area::getTileSet(const std::string& imagePath)
257 std::map<std::string, TileSet>::iterator it;
258 it = tileSets.find(imagePath);
259 if (it == tileSets.end()) {
260 Log::err("Area", "tileset " + imagePath + " not found");
261 return NULL;
263 return &tileSets[imagePath];
267 ivec3 Area::getDimensions() const
269 return dim;
272 ivec2 Area::getTileDimensions() const
274 return tileDim;
277 icube Area::visibleTileBounds() const
279 rvec2 screen = view->getVirtRes();
280 rvec2 off = view->getMapOffset();
282 int x1 = (int)floor(off.x / tileDim.x);
283 int y1 = (int)floor(off.y / tileDim.y);
284 int x2 = (int)ceil((screen.x + off.x) / tileDim.x);
285 int y2 = (int)ceil((screen.y + off.y) / tileDim.y);
287 return icube(x1, y1, 0, x2, y2, dim.z);
290 icube Area::visibleTiles() const
292 icube cube = visibleTileBounds();
293 if (!loopX) {
294 cube.x1 = std::max(cube.x1, 0);
295 cube.x2 = std::min(cube.x2, dim.x);
297 if (!loopY) {
298 cube.y1 = std::max(cube.y1, 0);
299 cube.y2 = std::min(cube.y2, dim.y);
301 return cube;
304 bool Area::inBounds(int x, int y, int z) const
306 return ((loopX || (0 <= x && x < dim.x)) &&
307 (loopY || (0 <= y && y < dim.y)) &&
308 0 <= z && z < dim.z);
311 bool Area::inBounds(int x, int y, double z) const
313 return inBounds(x, y, depthIndex(z));
316 bool Area::inBounds(icoord phys) const
318 return inBounds(phys.x, phys.y, phys.z);
321 bool Area::inBounds(vicoord virt) const
323 return inBounds(virt2phys(virt));
326 bool Area::inBounds(rcoord virt) const
328 return inBounds(virt2phys(virt));
331 bool Area::inBounds(Entity* ent) const
333 return inBounds(ent->getPixelCoord());
338 bool Area::loopsInX() const
340 return loopX;
343 bool Area::loopsInY() const
345 return loopY;
348 const std::string Area::getDescriptor() const
350 return descriptor;
353 Entity* Area::spawnEntity(const std::string& descriptor,
354 int x, int y, double z, const std::string& phase)
356 Entity* e = new Entity();
357 if (!e->init(descriptor)) {
358 // Error logged.
359 delete e;
360 return NULL;
362 e->setArea(this);
363 if (!e->setPhase(phase)) {
364 // Error logged.
365 delete e;
366 return NULL;
368 if (!inBounds(x, y, z)) {
369 Log::err(descriptor, boost::str(
370 boost::format("not in map bounds: x:%d y:%d z:%f")
371 % x % y % z
373 delete e;
374 return NULL;
376 e->setTileCoords(x, y, z);
377 insert(e);
378 return e;
381 void Area::insert(Entity* e)
383 entities.insert(e);
386 void Area::erase(Entity* e)
388 entities.erase(e);
393 vicoord Area::phys2virt_vi(icoord phys) const
395 return vicoord(phys.x, phys.y, indexDepth(phys.z));
398 rcoord Area::phys2virt_r(icoord phys) const
400 return rcoord(
401 (double)phys.x * tileDim.x,
402 (double)phys.y * tileDim.y,
403 indexDepth(phys.z)
407 icoord Area::virt2phys(vicoord virt) const
409 return icoord(virt.x, virt.y, depthIndex(virt.z));
412 icoord Area::virt2phys(rcoord virt) const
414 return icoord(
415 (int)(virt.x / tileDim.x),
416 (int)(virt.y / tileDim.y),
417 depthIndex(virt.z)
421 rcoord Area::virt2virt(vicoord virt) const
423 return rcoord(
424 (double)virt.x * tileDim.x,
425 (double)virt.y * tileDim.y,
426 virt.z
430 vicoord Area::virt2virt(rcoord virt) const
432 return vicoord(
433 (int)virt.x / tileDim.x,
434 (int)virt.y / tileDim.y,
435 virt.z
440 int Area::depthIndex(double depth) const
442 return depth2idx.find(depth)->second;
445 double Area::indexDepth(int idx) const
447 return idx2depth[idx];
452 void Area::runLoadScripts()
454 World* world = World::instance();
455 world->runAreaLoadScript(this);
457 pythonSetGlobal("Area", this);
458 loadScript.invoke();
461 void Area::updateTileAnimations()
463 const int millis = GameWindow::instance().time();
464 BOOST_FOREACH(tilesets_t::value_type& pair, tileSets) {
465 TileSet& set = pair.second;
466 int w = set.getWidth();
467 int h = set.getHeight();
468 for (int y = 0; y < h; y++) {
469 for (int x = 0; x < w; x++) {
470 TileType* type = set.get(x, y);
471 type->anim.updateFrame(millis);
477 void Area::drawTiles() const
479 const icube tiles = visibleTiles();
480 for (int z = tiles.z1; z < tiles.z2; z++) {
481 double depth = idx2depth[z];
482 for (int y = tiles.y1; y < tiles.y2; y++) {
483 for (int x = tiles.x1; x < tiles.x2; x++) {
484 const Tile* tile = getTile(x, y, z);
485 drawTile(*tile, x, y, depth);
491 void Area::drawTile(const Tile& tile, int x, int y, double depth) const
493 const TileType* type = (TileType*)tile.parent;
494 if (type) {
495 const Gosu::Image* img = type->anim.frame();
496 if (img)
497 img->draw((double)x*img->width(),
498 (double)y*img->height(), depth);
502 void Area::drawEntities()
504 BOOST_FOREACH(Entity* e, entities) {
505 e->draw();
507 player->draw();
510 void Area::drawColorOverlay()
512 if (colorOverlay.alpha() != 0) {
513 GameWindow& window = GameWindow::instance();
514 Gosu::Color c = colorOverlay;
515 int x = window.width();
516 int y = window.height();
517 window.graphics().drawQuad(
518 0, 0, c,
519 x, 0, c,
520 x, y, c,
521 0, y, c,
527 boost::python::tuple Area::pyGetDimensions()
529 using namespace boost::python;
531 list zs;
532 BOOST_FOREACH(double dep, idx2depth)
533 zs.append(dep);
534 return make_tuple(dim.x, dim.y, zs);
537 void exportArea()
539 using namespace boost::python;
541 class_<Area>("Area", no_init)
542 .add_property("descriptor", &Area::getDescriptor)
543 .add_property("dimensions", &Area::pyGetDimensions)
544 .def("redraw", &Area::requestRedraw)
545 .def("tileset", &Area::getTileSet,
546 return_value_policy<reference_existing_object>())
547 .def("tile",
548 static_cast<Tile* (Area::*) (int, int, double)>
549 (&Area::getTile),
550 return_value_policy<reference_existing_object>())
551 .def("in_bounds",
552 static_cast<bool (Area::*) (int, int, double) const>
553 (&Area::inBounds))
554 .def("color_overlay", &Area::setColorOverlay)
555 .def("new_entity", &Area::spawnEntity,
556 return_value_policy<reference_existing_object>())
557 .def_readwrite("on_focus", &Area::focusScript)
558 .def_readwrite("on_update", &Area::updateScript)