finish move to Python-handled imports
[Tsunagari.git] / src / area.cpp
blob134334e58774f004ca9fd14923943da488538ebd
1 /***************************************
2 ** Tsunagari Tile Engine **
3 ** area.cpp **
4 ** Copyright 2011-2013 PariahSoft LLC **
5 ***************************************/
7 // **********
8 // Permission is hereby granted, free of charge, to any person obtaining a copy
9 // of this software and associated documentation files (the "Software"), to
10 // deal in the Software without restriction, including without limitation the
11 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12 // sell copies of the Software, and to permit persons to whom the Software is
13 // furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24 // IN THE SOFTWARE.
25 // **********
27 #include <algorithm>
28 #include <math.h>
29 #include <stdlib.h> // for exit(1) on fatal
30 #include <vector>
32 #include <boost/shared_ptr.hpp>
33 #include <Gosu/Graphics.hpp>
34 #include <Gosu/Math.hpp>
35 #include <Gosu/Timing.hpp>
37 #include "area.h"
38 #include "client-conf.h"
39 #include "entity.h"
40 #include "formatter.h"
41 #include "log.h"
42 #include "image.h"
43 #include "music.h"
44 #include "npc.h"
45 #include "overlay.h"
46 #include "python.h"
47 #include "python-bindings-template.cpp"
48 #include "reader.h"
49 #include "tile.h"
50 #include "window.h"
51 #include "world.h"
53 #define ASSERT(x) if (!(x)) { return false; }
55 /* NOTE: In the TMX map format used by Tiled, tileset tiles start counting
56 their Y-positions from 0, while layer tiles start counting from 1. I
57 can't imagine why the author did this, but we have to take it into
58 account.
61 template<class T>
62 static T wrap(T min, T value, T max)
64 while (value < min)
65 value += max;
66 return value % max;
69 Area::Area(Viewport* view,
70 Player* player,
71 const std::string& descriptor)
72 : view(view),
73 player(player),
74 colorOverlay(0, 0, 0, 0),
75 dim(0, 0, 0),
76 tileDim(0, 0),
77 loopX(false), loopY(false),
78 beenFocused(false),
79 redraw(true),
80 descriptor(descriptor)
84 Area::~Area()
88 bool Area::init()
90 // Abstract method.
91 return false;
94 void Area::focus()
96 if (!beenFocused) {
97 beenFocused = true;
98 runLoadScripts();
101 if (musicIntroSet)
102 Music::setIntro(musicIntro);
103 if (musicLoopSet)
104 Music::setLoop(musicLoop);
106 pythonSetGlobal("Area", this);
107 if (focusScript)
108 focusScript->invoke();
111 void Area::buttonDown(const Gosu::Button btn)
113 if (btn == Gosu::kbRight)
114 player->startMovement(ivec2(1, 0));
115 else if (btn == Gosu::kbLeft)
116 player->startMovement(ivec2(-1, 0));
117 else if (btn == Gosu::kbUp)
118 player->startMovement(ivec2(0, -1));
119 else if (btn == Gosu::kbDown)
120 player->startMovement(ivec2(0, 1));
121 else if (btn == Gosu::kbSpace)
122 player->useTile();
125 void Area::buttonUp(const Gosu::Button btn)
127 if (btn == Gosu::kbRight)
128 player->stopMovement(ivec2(1, 0));
129 else if (btn == Gosu::kbLeft)
130 player->stopMovement(ivec2(-1, 0));
131 else if (btn == Gosu::kbUp)
132 player->stopMovement(ivec2(0, -1));
133 else if (btn == Gosu::kbDown)
134 player->stopMovement(ivec2(0, 1));
137 void Area::draw()
139 drawTiles();
140 drawEntities();
141 drawColorOverlay();
142 redraw = false;
145 bool Area::needsRedraw() const
147 if (redraw)
148 return true;
149 if (player->needsRedraw())
150 return true;
152 for (CharacterSet::iterator it = characters.begin(); it != characters.end(); it++) {
153 Character* c = *it;
154 if (c->needsRedraw())
155 return true;
157 for (OverlaySet::iterator it = overlays.begin(); it != overlays.end(); it++) {
158 Overlay* o = *it;
159 if (o->needsRedraw())
160 return true;
163 // Do any on-screen tile types need to update their animations?
164 const icube tiles = visibleTiles();
165 for (int z = tiles.z1; z < tiles.z2; z++) {
166 for (int y = tiles.y1; y < tiles.y2; y++) {
167 for (int x = tiles.x1; x < tiles.x2; x++) {
168 const Tile* tile = getTile(x, y, z);
169 const TileType* type = tile->getType();
170 if (type && type->needsRedraw())
171 return true;
175 return false;
178 void Area::requestRedraw()
180 redraw = true;
183 void Area::tick(unsigned long dt)
185 pythonSetGlobal("Area", this);
186 if (tickScript)
187 tickScript->invoke();
189 for (OverlaySet::iterator it = overlays.begin(); it != overlays.end(); it++) {
190 Overlay* o = *it;
191 pythonSetGlobal("Area", this);
192 o->tick(dt);
195 if (conf.moveMode != TURN) {
196 pythonSetGlobal("Area", this);
197 player->tick(dt);
199 for (CharacterSet::iterator it = characters.begin(); it != characters.end(); it++) {
200 Character* c = *it;
201 pythonSetGlobal("Area", this);
202 c->tick(dt);
206 view->tick(dt);
207 Music::tick();
210 void Area::turn()
212 pythonSetGlobal("Area", this);
213 if (turnScript)
214 turnScript->invoke();
216 pythonSetGlobal("Area", this);
217 player->turn();
219 for (CharacterSet::iterator it = characters.begin(); it != characters.end(); it++) {
220 Character* c = *it;
221 pythonSetGlobal("Area", this);
222 c->turn();
225 view->turn();
228 // Python API.
229 void Area::setColorOverlay(int r, int g, int b, int a)
231 using namespace Gosu;
233 if (0 <= r && r < 256 &&
234 0 <= g && g < 256 &&
235 0 <= b && b < 256 &&
236 0 <= a && a < 256) {
237 Color::Channel ac = (Color::Channel)a;
238 Color::Channel rc = (Color::Channel)r;
239 Color::Channel gc = (Color::Channel)g;
240 Color::Channel bc = (Color::Channel)b;
241 colorOverlay = Color(ac, rc, gc, bc);
242 redraw = true;
244 else {
245 PyErr_Format(PyExc_ValueError,
246 "Area::color_overlay() arguments must be "
247 "between 0 and 255");
253 const Tile* Area::getTile(int x, int y, int z) const
255 if (loopX)
256 x = wrap(0, x, dim.x);
257 if (loopY)
258 y = wrap(0, y, dim.y);
259 if (inBounds(x, y, z))
260 return &map[z][y][x];
261 else
262 return NULL;
265 const Tile* Area::getTile(int x, int y, double z) const
267 return getTile(x, y, depthIndex(z));
270 const Tile* Area::getTile(icoord phys) const
272 return getTile(phys.x, phys.y, phys.z);
275 const Tile* Area::getTile(vicoord virt) const
277 return getTile(virt2phys(virt));
280 const Tile* Area::getTile(rcoord virt) const
282 return getTile(virt2phys(virt));
285 Tile* Area::getTile(int x, int y, int z)
287 if (loopX)
288 x = wrap(0, x, dim.x);
289 if (loopY)
290 y = wrap(0, y, dim.y);
291 if (inBounds(x, y, z))
292 return &map[z][y][x];
293 else
294 return NULL;
297 Tile* Area::getTile(int x, int y, double z)
299 return getTile(x, y, depthIndex(z));
302 Tile* Area::getTile(icoord phys)
304 return getTile(phys.x, phys.y, phys.z);
307 Tile* Area::getTile(vicoord virt)
309 return getTile(virt2phys(virt));
312 Tile* Area::getTile(rcoord virt)
314 return getTile(virt2phys(virt));
317 TileSet* Area::getTileSet(const std::string& imagePath)
319 std::map<std::string, TileSet>::iterator it;
320 it = tileSets.find(imagePath);
321 if (it == tileSets.end()) {
322 Log::err("Area", "tileset " + imagePath + " not found");
323 return NULL;
325 return &tileSets[imagePath];
329 ivec3 Area::getDimensions() const
331 return dim;
334 ivec2 Area::getTileDimensions() const
336 return tileDim;
339 double Area::isometricZOff(rvec2 pos) const
341 return pos.y / tileDim.y * ISOMETRIC_ZOFF_PER_TILE;
344 icube Area::visibleTileBounds() const
346 rvec2 screen = view->getVirtRes();
347 rvec2 off = view->getMapOffset();
349 int x1 = (int)floor(off.x / tileDim.x);
350 int y1 = (int)floor(off.y / tileDim.y);
351 int x2 = (int)ceil((screen.x + off.x) / tileDim.x);
352 int y2 = (int)ceil((screen.y + off.y) / tileDim.y);
354 return icube(x1, y1, 0, x2, y2, dim.z);
357 icube Area::visibleTiles() const
359 icube cube = visibleTileBounds();
360 if (!loopX) {
361 cube.x1 = std::max(cube.x1, 0);
362 cube.x2 = std::min(cube.x2, dim.x);
364 if (!loopY) {
365 cube.y1 = std::max(cube.y1, 0);
366 cube.y2 = std::min(cube.y2, dim.y);
368 return cube;
371 bool Area::inBounds(int x, int y, int z) const
373 return ((loopX || (0 <= x && x < dim.x)) &&
374 (loopY || (0 <= y && y < dim.y)) &&
375 0 <= z && z < dim.z);
378 bool Area::inBounds(int x, int y, double z) const
380 return inBounds(x, y, depthIndex(z));
383 bool Area::inBounds(icoord phys) const
385 return inBounds(phys.x, phys.y, phys.z);
388 bool Area::inBounds(vicoord virt) const
390 return inBounds(virt2phys(virt));
393 bool Area::inBounds(rcoord virt) const
395 return inBounds(virt2phys(virt));
398 bool Area::inBounds(Entity* ent) const
400 return inBounds(ent->getPixelCoord());
405 bool Area::loopsInX() const
407 return loopX;
410 bool Area::loopsInY() const
412 return loopY;
415 const std::string Area::getDescriptor() const
417 return descriptor;
420 Entity* Area::spawnNPC(const std::string& descriptor,
421 int x, int y, double z, const std::string& phase)
423 Character* c = new NPC();
424 if (!c->init(descriptor)) {
425 // Error logged.
426 delete c;
427 return NULL;
429 c->setArea(this);
430 if (!c->setPhase(phase)) {
431 // Error logged.
432 delete c;
433 return NULL;
435 c->setTileCoords(x, y, z);
436 insert(c);
437 return c;
440 Entity* Area::spawnOverlay(const std::string& descriptor,
441 int x, int y, double z, const std::string& phase)
443 Overlay* o = new Overlay();
444 if (!o->init(descriptor)) {
445 // Error logged.
446 delete o;
447 return NULL;
449 o->setArea(this);
450 if (!o->setPhase(phase)) {
451 // Error logged.
452 delete o;
453 return NULL;
455 o->setTileCoords(x, y, z);
456 // XXX: o->leaveTile(); // Overlays don't consume tiles.
458 insert(o);
459 return o;
462 void Area::insert(Character* c)
464 characters.insert(c);
467 void Area::insert(Overlay* o)
469 overlays.insert(o);
472 void Area::erase(Character* c)
474 characters.erase(c);
477 void Area::erase(Overlay* o)
479 overlays.erase(o);
484 vicoord Area::phys2virt_vi(icoord phys) const
486 return vicoord(phys.x, phys.y, indexDepth(phys.z));
489 rcoord Area::phys2virt_r(icoord phys) const
491 return rcoord(
492 (double)phys.x * tileDim.x,
493 (double)phys.y * tileDim.y,
494 indexDepth(phys.z)
498 icoord Area::virt2phys(vicoord virt) const
500 return icoord(virt.x, virt.y, depthIndex(virt.z));
503 icoord Area::virt2phys(rcoord virt) const
505 return icoord(
506 (int)(virt.x / tileDim.x),
507 (int)(virt.y / tileDim.y),
508 depthIndex(virt.z)
512 rcoord Area::virt2virt(vicoord virt) const
514 return rcoord(
515 (double)virt.x * tileDim.x,
516 (double)virt.y * tileDim.y,
517 virt.z
521 vicoord Area::virt2virt(rcoord virt) const
523 return vicoord(
524 (int)virt.x / tileDim.x,
525 (int)virt.y / tileDim.y,
526 virt.z
531 int Area::depthIndex(double depth) const
533 using namespace boost;
535 std::map<double, int>::const_iterator it;
536 it = depth2idx.find(depth);
537 if (it == depth2idx.end()) {
538 Log::fatal(descriptor, Formatter(
539 "attempt to access invalid layer: %") % depth);
540 exit(-1);
542 return it->second;
545 double Area::indexDepth(int idx) const
547 return idx2depth[idx];
552 void Area::runLoadScripts()
554 World* world = World::instance();
555 world->runAreaLoadScript(this);
557 pythonSetGlobal("Area", this);
558 if (loadScript)
559 loadScript->invoke();
562 void Area::drawTiles()
564 icube tiles = visibleTiles();
565 for (int z = tiles.z1; z < tiles.z2; z++) {
566 double depth = idx2depth[z];
567 for (int y = tiles.y1; y < tiles.y2; y++) {
568 for (int x = tiles.x1; x < tiles.x2; x++) {
569 Tile* tile = getTile(x, y, z);
570 // We are certain the Tile exists.
571 drawTile(*tile, x, y, depth);
577 void Area::drawTile(Tile& tile, int x, int y, double depth)
579 TileType* type = (TileType*)tile.parent;
580 if (type) {
581 time_t now = World::instance()->time();
582 const Image* img = type->anim.frame(now);
583 if (img) {
584 rvec2 drawPos(
585 double(x * (int)img->width()),
586 double(y * (int)img->height())
588 img->draw(drawPos.x, drawPos.y,
589 depth + isometricZOff(drawPos));
594 void Area::drawEntities()
596 for (CharacterSet::iterator it = characters.begin(); it != characters.end(); it++) {
597 Character* c = *it;
598 c->draw();
600 for (OverlaySet::iterator it = overlays.begin(); it != overlays.end(); it++) {
601 Overlay* o = *it;
602 o->draw();
604 player->draw();
607 void Area::drawColorOverlay()
609 if (colorOverlay.alpha() != 0) {
610 GameWindow& window = GameWindow::instance();
611 Gosu::Color c = colorOverlay;
612 int x = window.width();
613 int y = window.height();
614 window.graphics().drawQuad(
615 0, 0, c,
616 x, 0, c,
617 x, y, c,
618 0, y, c,
624 /* FIXME: Don't expose boost::python::tuple to the header file. */
625 //boost::python::tuple Area::pyGetDimensions()
627 // using namespace boost::python;
629 // list zs;
630 // BOOST_FOREACH(double dep, idx2depth)
631 // zs.append(dep);
632 // return make_tuple(dim.x, dim.y, zs);
635 void exportArea()
637 using namespace boost::python;
639 class_<Area>("Area", no_init)
640 .add_property("descriptor", &Area::getDescriptor)
641 // .add_property("dimensions", &Area::pyGetDimensions)
642 .def("redraw", &Area::requestRedraw)
643 .def("tileset", &Area::getTileSet,
644 return_value_policy<reference_existing_object>())
645 .def("tile",
646 static_cast<Tile* (Area::*) (int, int, double)>
647 (&Area::getTile),
648 return_value_policy<reference_existing_object>())
649 .def("in_bounds",
650 static_cast<bool (Area::*) (int, int, double) const>
651 (&Area::inBounds))
652 .def("color_overlay", &Area::setColorOverlay)
653 .def("new_npc", &Area::spawnNPC,
654 return_value_policy<reference_existing_object>())
655 .def("new_overlay", &Area::spawnOverlay,
656 return_value_policy<reference_existing_object>())
657 // .def_readwrite("on_focus", &Area::focusScript)
658 // .def_readwrite("on_tick", &Area::tickScript)
659 // .def_readwrite("on_turn", &Area::turnScript)