FEATURE: entity.on_delete; NPCs die when exiting
[Tsunagari.git] / src / area.cpp
blob9e7ff1d4b2f433e995c0ef2d98e16c011d7571ed
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 "npc.h"
24 #include "python.h"
25 #include "resourcer.h"
26 #include "tile.h"
27 #include "window.h"
28 #include "world.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
35 account.
38 template<class T>
39 static T wrap(T min, T value, T max)
41 while (value < min)
42 value += max;
43 return value % max;
46 Area::Area(Viewport* view,
47 Player* player,
48 Music* music,
49 const std::string& descriptor)
50 : view(view),
51 player(player),
52 music(music),
53 colorOverlay(0, 0, 0, 0),
54 dim(0, 0, 0),
55 tileDim(0, 0),
56 loopX(false), loopY(false),
57 beenFocused(false),
58 redraw(true),
59 descriptor(descriptor)
63 Area::~Area()
67 bool Area::init()
69 // Abstract method.
70 return false;
73 void Area::focus()
75 if (!beenFocused) {
76 beenFocused = true;
77 runLoadScripts();
80 music->setIntro(musicIntro);
81 music->setLoop(musicLoop);
83 pythonSetGlobal("Area", this);
84 focusScript.invoke();
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)
98 player->useTile();
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));
113 void Area::draw()
115 updateTileAnimations();
116 drawTiles();
117 drawEntities();
118 drawColorOverlay();
119 redraw = false;
122 bool Area::needsRedraw() const
124 if (redraw)
125 return true;
126 if (player->needsRedraw())
127 return true;
129 BOOST_FOREACH(Entity* e, entities)
130 if (e->needsRedraw())
131 return true;
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())
141 return true;
145 return false;
148 void Area::requestRedraw()
150 redraw = true;
153 void Area::update(unsigned long dt)
155 pythonSetGlobal("Area", this);
156 updateScript.invoke();
158 pythonSetGlobal("Area", this);
159 player->update(dt);
161 BOOST_FOREACH(Entity* e, entities)
162 e->update(dt);
164 view->update(dt);
165 music->update();
168 void Area::setColorOverlay(int r, int g, int b, int a)
170 using namespace Gosu;
172 if (0 <= r && r < 256 &&
173 0 <= g && g < 256 &&
174 0 <= b && b < 256 &&
175 0 <= a && a < 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);
181 redraw = true;
183 else {
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
194 if (loopX)
195 x = wrap(0, x, dim.x);
196 if (loopY)
197 y = wrap(0, y, dim.y);
198 if (inBounds(x, y, z))
199 return &map[z][y][x];
200 else
201 return NULL;
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)
226 if (loopX)
227 x = wrap(0, x, dim.x);
228 if (loopY)
229 y = wrap(0, y, dim.y);
230 if (inBounds(x, y, z))
231 return &map[z][y][x];
232 else
233 return NULL;
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");
262 return NULL;
264 return &tileSets[imagePath];
268 ivec3 Area::getDimensions() const
270 return dim;
273 ivec2 Area::getTileDimensions() const
275 return tileDim;
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();
294 if (!loopX) {
295 cube.x1 = std::max(cube.x1, 0);
296 cube.x2 = std::min(cube.x2, dim.x);
298 if (!loopY) {
299 cube.y1 = std::max(cube.y1, 0);
300 cube.y2 = std::min(cube.y2, dim.y);
302 return cube;
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
341 return loopX;
344 bool Area::loopsInY() const
346 return loopY;
349 const std::string Area::getDescriptor() const
351 return descriptor;
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)) {
359 // Error logged.
360 delete e;
361 return NULL;
363 e->setArea(this);
364 if (!e->setPhase(phase)) {
365 // Error logged.
366 delete e;
367 return NULL;
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")
372 % x % y % z
374 delete e;
375 return NULL;
377 e->setTileCoords(x, y, z);
378 insert(e);
379 return e;
382 void Area::insert(Entity* e)
384 entities.insert(e);
387 void Area::erase(Entity* e)
389 entities.erase(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
401 return rcoord(
402 (double)phys.x * tileDim.x,
403 (double)phys.y * tileDim.y,
404 indexDepth(phys.z)
408 icoord Area::virt2phys(vicoord virt) const
410 return icoord(virt.x, virt.y, depthIndex(virt.z));
413 icoord Area::virt2phys(rcoord virt) const
415 return icoord(
416 (int)(virt.x / tileDim.x),
417 (int)(virt.y / tileDim.y),
418 depthIndex(virt.z)
422 rcoord Area::virt2virt(vicoord virt) const
424 return rcoord(
425 (double)virt.x * tileDim.x,
426 (double)virt.y * tileDim.y,
427 virt.z
431 vicoord Area::virt2virt(rcoord virt) const
433 return vicoord(
434 (int)virt.x / tileDim.x,
435 (int)virt.y / tileDim.y,
436 virt.z
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);
459 loadScript.invoke();
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;
495 if (type) {
496 const Gosu::Image* img = type->anim.frame();
497 if (img)
498 img->draw((double)x*img->width(),
499 (double)y*img->height(), depth);
503 void Area::drawEntities()
505 BOOST_FOREACH(Entity* e, entities) {
506 e->draw();
508 player->draw();
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(
519 0, 0, c,
520 x, 0, c,
521 x, y, c,
522 0, y, c,
528 boost::python::tuple Area::pyGetDimensions()
530 using namespace boost::python;
532 list zs;
533 BOOST_FOREACH(double dep, idx2depth)
534 zs.append(dep);
535 return make_tuple(dim.x, dim.y, zs);
538 void exportArea()
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>())
548 .def("tile",
549 static_cast<Tile* (Area::*) (int, int, double)>
550 (&Area::getTile),
551 return_value_policy<reference_existing_object>())
552 .def("in_bounds",
553 static_cast<bool (Area::*) (int, int, double) const>
554 (&Area::inBounds))
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)