Fix INT_MAX definition on some newer systems.
[Tsunagari.git] / src / area.cpp
blob04577162fce72babae6a707c6a9e465300d2d005
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
31 #include <Gosu/Graphics.hpp>
32 #include <Gosu/Math.hpp>
33 #include <Gosu/Timing.hpp>
35 #include "area.h"
36 #include "client-conf.h"
37 #include "entity.h"
38 #include "formatter.h"
39 #include "log.h"
40 #include "image.h"
41 #include "music.h"
42 #include "npc.h"
43 #include "overlay.h"
44 #include "python.h"
45 #include "python-bindings-template.cpp"
46 #include "reader.h"
47 #include "tile.h"
48 #include "window.h"
49 #include "world.h"
51 #define ASSERT(x) if (!(x)) { return false; }
53 /* NOTE: In the TMX map format used by Tiled, tileset tiles start counting
54 their Y-positions from 0, while layer tiles start counting from 1. I
55 can't imagine why the author did this, but we have to take it into
56 account.
59 template<class T>
60 static T wrap(T min, T value, T max)
62 while (value < min)
63 value += max;
64 return value % max;
67 Area::Area(Viewport* view,
68 Player* player,
69 const std::string& descriptor)
70 : view(view),
71 player(player),
72 colorOverlay(0, 0, 0, 0),
73 dim(0, 0, 0),
74 tileDim(0, 0),
75 loopX(false), loopY(false),
76 beenFocused(false),
77 redraw(true),
78 descriptor(descriptor)
82 Area::~Area()
86 bool Area::init()
88 // Abstract method.
89 return false;
92 void Area::focus()
94 if (!beenFocused) {
95 beenFocused = true;
96 runLoadScripts();
99 if (musicIntroSet)
100 World::instance()->getMusic()->setIntro(musicIntro);
101 if (musicLoopSet)
102 World::instance()->getMusic()->setLoop(musicLoop);
104 pythonSetGlobal("Area", this);
105 if (focusScript)
106 focusScript->invoke();
109 void Area::buttonDown(const Gosu::Button btn)
111 if (btn == Gosu::kbRight)
112 player->startMovement(ivec2(1, 0));
113 else if (btn == Gosu::kbLeft)
114 player->startMovement(ivec2(-1, 0));
115 else if (btn == Gosu::kbUp)
116 player->startMovement(ivec2(0, -1));
117 else if (btn == Gosu::kbDown)
118 player->startMovement(ivec2(0, 1));
119 else if (btn == Gosu::kbSpace)
120 player->useTile();
123 void Area::buttonUp(const Gosu::Button btn)
125 if (btn == Gosu::kbRight)
126 player->stopMovement(ivec2(1, 0));
127 else if (btn == Gosu::kbLeft)
128 player->stopMovement(ivec2(-1, 0));
129 else if (btn == Gosu::kbUp)
130 player->stopMovement(ivec2(0, -1));
131 else if (btn == Gosu::kbDown)
132 player->stopMovement(ivec2(0, 1));
135 void Area::draw()
137 drawTiles();
138 drawEntities();
139 drawColorOverlay();
140 redraw = false;
143 bool Area::needsRedraw() const
145 if (redraw)
146 return true;
147 if (player->needsRedraw())
148 return true;
150 for (CharacterSet::iterator it = characters.begin(); it != characters.end(); it++) {
151 Character* c = *it;
152 if (c->needsRedraw())
153 return true;
155 for (OverlaySet::iterator it = overlays.begin(); it != overlays.end(); it++) {
156 Overlay* o = *it;
157 if (o->needsRedraw())
158 return true;
161 // Do any on-screen tile types need to update their animations?
162 const icube tiles = visibleTiles();
163 for (int z = tiles.z1; z < tiles.z2; z++) {
164 for (int y = tiles.y1; y < tiles.y2; y++) {
165 for (int x = tiles.x1; x < tiles.x2; x++) {
166 const Tile* tile = getTile(x, y, z);
167 const TileType* type = tile->getType();
168 if (type && type->needsRedraw())
169 return true;
173 return false;
176 void Area::requestRedraw()
178 redraw = true;
181 void Area::tick(unsigned long dt)
183 pythonSetGlobal("Area", this);
184 if (tickScript)
185 tickScript->invoke();
187 for (OverlaySet::iterator it = overlays.begin(); it != overlays.end(); it++) {
188 Overlay* o = *it;
189 pythonSetGlobal("Area", this);
190 o->tick(dt);
193 if (conf.moveMode != TURN) {
194 pythonSetGlobal("Area", this);
195 player->tick(dt);
197 for (CharacterSet::iterator it = characters.begin(); it != characters.end(); it++) {
198 Character* c = *it;
199 pythonSetGlobal("Area", this);
200 c->tick(dt);
204 view->tick(dt);
205 World::instance()->getMusic()->tick();
208 void Area::turn()
210 pythonSetGlobal("Area", this);
211 if (turnScript)
212 turnScript->invoke();
214 pythonSetGlobal("Area", this);
215 player->turn();
217 for (CharacterSet::iterator it = characters.begin(); it != characters.end(); it++) {
218 Character* c = *it;
219 pythonSetGlobal("Area", this);
220 c->turn();
223 view->turn();
226 // Python API.
227 void Area::setColorOverlay(int r, int g, int b, int a)
229 using namespace Gosu;
231 if (0 <= r && r < 256 &&
232 0 <= g && g < 256 &&
233 0 <= b && b < 256 &&
234 0 <= a && a < 256) {
235 Color::Channel ac = (Color::Channel)a;
236 Color::Channel rc = (Color::Channel)r;
237 Color::Channel gc = (Color::Channel)g;
238 Color::Channel bc = (Color::Channel)b;
239 colorOverlay = Color(ac, rc, gc, bc);
240 redraw = true;
242 else {
243 PyErr_Format(PyExc_ValueError,
244 "Area::color_overlay() arguments must be "
245 "between 0 and 255");
251 const Tile* Area::getTile(int x, int y, int z) const
253 if (loopX)
254 x = wrap(0, x, dim.x);
255 if (loopY)
256 y = wrap(0, y, dim.y);
257 if (inBounds(x, y, z))
258 return &map[z][y][x];
259 else
260 return NULL;
263 const Tile* Area::getTile(int x, int y, double z) const
265 return getTile(x, y, depthIndex(z));
268 const Tile* Area::getTile(icoord phys) const
270 return getTile(phys.x, phys.y, phys.z);
273 const Tile* Area::getTile(vicoord virt) const
275 return getTile(virt2phys(virt));
278 const Tile* Area::getTile(rcoord virt) const
280 return getTile(virt2phys(virt));
283 Tile* Area::getTile(int x, int y, int z)
285 if (loopX)
286 x = wrap(0, x, dim.x);
287 if (loopY)
288 y = wrap(0, y, dim.y);
289 if (inBounds(x, y, z))
290 return &map[z][y][x];
291 else
292 return NULL;
295 Tile* Area::getTile(int x, int y, double z)
297 return getTile(x, y, depthIndex(z));
300 Tile* Area::getTile(icoord phys)
302 return getTile(phys.x, phys.y, phys.z);
305 Tile* Area::getTile(vicoord virt)
307 return getTile(virt2phys(virt));
310 Tile* Area::getTile(rcoord virt)
312 return getTile(virt2phys(virt));
315 TileSet* Area::getTileSet(const std::string& imagePath)
317 std::map<std::string, TileSet>::iterator it;
318 it = tileSets.find(imagePath);
319 if (it == tileSets.end()) {
320 Log::err("Area", "tileset " + imagePath + " not found");
321 return NULL;
323 return &tileSets[imagePath];
327 ivec3 Area::getDimensions() const
329 return dim;
332 ivec2 Area::getTileDimensions() const
334 return tileDim;
337 double Area::isometricZOff(rvec2 pos) const
339 return pos.y / tileDim.y * ISOMETRIC_ZOFF_PER_TILE;
342 icube Area::visibleTileBounds() const
344 rvec2 screen = view->getVirtRes();
345 rvec2 off = view->getMapOffset();
347 int x1 = (int)floor(off.x / tileDim.x);
348 int y1 = (int)floor(off.y / tileDim.y);
349 int x2 = (int)ceil((screen.x + off.x) / tileDim.x);
350 int y2 = (int)ceil((screen.y + off.y) / tileDim.y);
352 return icube(x1, y1, 0, x2, y2, dim.z);
355 icube Area::visibleTiles() const
357 icube cube = visibleTileBounds();
358 if (!loopX) {
359 cube.x1 = std::max(cube.x1, 0);
360 cube.x2 = std::min(cube.x2, dim.x);
362 if (!loopY) {
363 cube.y1 = std::max(cube.y1, 0);
364 cube.y2 = std::min(cube.y2, dim.y);
366 return cube;
369 bool Area::inBounds(int x, int y, int z) const
371 return ((loopX || (0 <= x && x < dim.x)) &&
372 (loopY || (0 <= y && y < dim.y)) &&
373 0 <= z && z < dim.z);
376 bool Area::inBounds(int x, int y, double z) const
378 return inBounds(x, y, depthIndex(z));
381 bool Area::inBounds(icoord phys) const
383 return inBounds(phys.x, phys.y, phys.z);
386 bool Area::inBounds(vicoord virt) const
388 return inBounds(virt2phys(virt));
391 bool Area::inBounds(rcoord virt) const
393 return inBounds(virt2phys(virt));
396 bool Area::inBounds(Entity* ent) const
398 return inBounds(ent->getPixelCoord());
403 bool Area::loopsInX() const
405 return loopX;
408 bool Area::loopsInY() const
410 return loopY;
413 const std::string Area::getDescriptor() const
415 return descriptor;
418 Entity* Area::spawnNPC(const std::string& descriptor,
419 int x, int y, double z, const std::string& phase)
421 Character* c = new NPC();
422 if (!c->init(descriptor)) {
423 // Error logged.
424 delete c;
425 return NULL;
427 c->setArea(this);
428 if (!c->setPhase(phase)) {
429 // Error logged.
430 delete c;
431 return NULL;
433 c->setTileCoords(x, y, z);
434 insert(c);
435 return c;
438 Entity* Area::spawnOverlay(const std::string& descriptor,
439 int x, int y, double z, const std::string& phase)
441 Overlay* o = new Overlay();
442 if (!o->init(descriptor)) {
443 // Error logged.
444 delete o;
445 return NULL;
447 o->setArea(this);
448 if (!o->setPhase(phase)) {
449 // Error logged.
450 delete o;
451 return NULL;
453 o->setTileCoords(x, y, z);
454 // XXX: o->leaveTile(); // Overlays don't consume tiles.
456 insert(o);
457 return o;
460 void Area::insert(Character* c)
462 characters.insert(c);
465 void Area::insert(Overlay* o)
467 overlays.insert(o);
470 void Area::erase(Character* c)
472 characters.erase(c);
475 void Area::erase(Overlay* o)
477 overlays.erase(o);
482 vicoord Area::phys2virt_vi(icoord phys) const
484 return vicoord(phys.x, phys.y, indexDepth(phys.z));
487 rcoord Area::phys2virt_r(icoord phys) const
489 return rcoord(
490 (double)phys.x * tileDim.x,
491 (double)phys.y * tileDim.y,
492 indexDepth(phys.z)
496 icoord Area::virt2phys(vicoord virt) const
498 return icoord(virt.x, virt.y, depthIndex(virt.z));
501 icoord Area::virt2phys(rcoord virt) const
503 return icoord(
504 (int)(virt.x / tileDim.x),
505 (int)(virt.y / tileDim.y),
506 depthIndex(virt.z)
510 rcoord Area::virt2virt(vicoord virt) const
512 return rcoord(
513 (double)virt.x * tileDim.x,
514 (double)virt.y * tileDim.y,
515 virt.z
519 vicoord Area::virt2virt(rcoord virt) const
521 return vicoord(
522 (int)virt.x / tileDim.x,
523 (int)virt.y / tileDim.y,
524 virt.z
529 int Area::depthIndex(double depth) const
531 std::map<double, int>::const_iterator it;
532 it = depth2idx.find(depth);
533 if (it == depth2idx.end()) {
534 Log::fatal(descriptor, Formatter(
535 "attempt to access invalid layer: %") % depth);
536 exit(-1);
538 return it->second;
541 double Area::indexDepth(int idx) const
543 return idx2depth[idx];
548 void Area::runLoadScripts()
550 World* world = World::instance();
551 world->runAreaLoadScript(this);
553 pythonSetGlobal("Area", this);
554 if (loadScript)
555 loadScript->invoke();
558 void Area::drawTiles()
560 icube tiles = visibleTiles();
561 for (int z = tiles.z1; z < tiles.z2; z++) {
562 double depth = idx2depth[z];
563 for (int y = tiles.y1; y < tiles.y2; y++) {
564 for (int x = tiles.x1; x < tiles.x2; x++) {
565 Tile* tile = getTile(x, y, z);
566 // We are certain the Tile exists.
567 drawTile(*tile, x, y, depth);
573 void Area::drawTile(Tile& tile, int x, int y, double depth)
575 TileType* type = (TileType*)tile.parent;
576 if (type) {
577 time_t now = World::instance()->time();
578 const Image* img = type->anim.frame(now);
579 if (img) {
580 rvec2 drawPos(
581 double(x * (int)img->width()),
582 double(y * (int)img->height())
584 img->draw(drawPos.x, drawPos.y,
585 depth + isometricZOff(drawPos));
590 void Area::drawEntities()
592 for (CharacterSet::iterator it = characters.begin(); it != characters.end(); it++) {
593 Character* c = *it;
594 c->draw();
596 for (OverlaySet::iterator it = overlays.begin(); it != overlays.end(); it++) {
597 Overlay* o = *it;
598 o->draw();
600 player->draw();
603 void Area::drawColorOverlay()
605 if (colorOverlay.alpha() != 0) {
606 GameWindow& window = GameWindow::instance();
607 Gosu::Color c = colorOverlay;
608 int x = window.width();
609 int y = window.height();
610 window.graphics().drawQuad(
611 0, 0, c,
612 x, 0, c,
613 x, y, c,
614 0, y, c,
620 /* FIXME: Don't expose boost::python::tuple to the header file. */
621 //boost::python::tuple Area::pyGetDimensions()
623 // using namespace boost::python;
625 // list zs;
626 // BOOST_FOREACH(double dep, idx2depth)
627 // zs.append(dep);
628 // return make_tuple(dim.x, dim.y, zs);
631 void exportArea()
633 using namespace boost::python;
635 class_<Area>("Area", no_init)
636 .add_property("descriptor", &Area::getDescriptor)
637 // .add_property("dimensions", &Area::pyGetDimensions)
638 .def("redraw", &Area::requestRedraw)
639 .def("tileset", &Area::getTileSet,
640 return_value_policy<reference_existing_object>())
641 .def("tile",
642 static_cast<Tile* (Area::*) (int, int, double)>
643 (&Area::getTile),
644 return_value_policy<reference_existing_object>())
645 .def("in_bounds",
646 static_cast<bool (Area::*) (int, int, double) const>
647 (&Area::inBounds))
648 .def("color_overlay", &Area::setColorOverlay)
649 .def("new_npc", &Area::spawnNPC,
650 return_value_policy<reference_existing_object>())
651 .def("new_overlay", &Area::spawnOverlay,
652 return_value_policy<reference_existing_object>())
653 // .def_readwrite("on_focus", &Area::focusScript)
654 // .def_readwrite("on_tick", &Area::tickScript)
655 // .def_readwrite("on_turn", &Area::turnScript)