Add documentation on placing tiles.
[Tsunagari.git] / src / area.cpp
blob95d502cf7335df8d61719728dc8a8496bc21efac
1 /*********************************
2 ** Tsunagari Tile Engine **
3 ** area.cpp **
4 ** Copyright 2011-2012 OmegaSDG **
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/foreach.hpp>
33 #include <boost/format.hpp>
34 #include <boost/shared_ptr.hpp>
35 #include <Gosu/Graphics.hpp>
36 #include <Gosu/Math.hpp>
37 #include <Gosu/Timing.hpp>
39 #include "area.h"
40 #include "entity.h"
41 #include "log.h"
42 #include "image.h"
43 #include "npc.h"
44 #include "overlay.h"
45 #include "python.h"
46 #include "resourcer.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 Music* music,
70 const std::string& descriptor)
71 : view(view),
72 player(player),
73 music(music),
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 (musicIntro)
102 music->setIntro(musicIntro.get());
103 if (musicLoop)
104 music->setLoop(musicLoop.get());
106 pythonSetGlobal("Area", this);
107 focusScript.invoke();
110 void Area::buttonDown(const Gosu::Button btn)
112 if (btn == Gosu::kbRight)
113 player->startMovement(ivec2(1, 0));
114 else if (btn == Gosu::kbLeft)
115 player->startMovement(ivec2(-1, 0));
116 else if (btn == Gosu::kbUp)
117 player->startMovement(ivec2(0, -1));
118 else if (btn == Gosu::kbDown)
119 player->startMovement(ivec2(0, 1));
120 else if (btn == Gosu::kbSpace)
121 player->useTile();
124 void Area::buttonUp(const Gosu::Button btn)
126 if (btn == Gosu::kbRight)
127 player->stopMovement(ivec2(1, 0));
128 else if (btn == Gosu::kbLeft)
129 player->stopMovement(ivec2(-1, 0));
130 else if (btn == Gosu::kbUp)
131 player->stopMovement(ivec2(0, -1));
132 else if (btn == Gosu::kbDown)
133 player->stopMovement(ivec2(0, 1));
136 void Area::draw()
138 drawTiles();
139 drawEntities();
140 drawColorOverlay();
141 redraw = false;
144 bool Area::needsRedraw() const
146 if (redraw)
147 return true;
148 if (player->needsRedraw())
149 return true;
151 BOOST_FOREACH(Character* c, characters)
152 if (c->needsRedraw())
153 return true;
154 BOOST_FOREACH(Overlay* o, overlays)
155 if (o->needsRedraw())
156 return true;
158 // Do any on-screen tile types need to update their animations?
159 const icube tiles = visibleTiles();
160 for (int z = tiles.z1; z < tiles.z2; z++) {
161 for (int y = tiles.y1; y < tiles.y2; y++) {
162 for (int x = tiles.x1; x < tiles.x2; x++) {
163 const Tile* tile = getTile(x, y, z);
164 const TileType* type = tile->getType();
165 if (type && type->needsRedraw())
166 return true;
170 return false;
173 void Area::requestRedraw()
175 redraw = true;
178 void Area::tick(unsigned long dt)
180 pythonSetGlobal("Area", this);
181 tickScript.invoke();
183 BOOST_FOREACH(Overlay* o, overlays) {
184 pythonSetGlobal("Area", this);
185 o->tick(dt);
188 if (conf.moveMode != TURN) {
189 pythonSetGlobal("Area", this);
190 player->tick(dt);
192 BOOST_FOREACH(Character* c, characters) {
193 pythonSetGlobal("Area", this);
194 c->tick(dt);
198 view->tick(dt);
199 music->tick();
202 void Area::turn()
204 pythonSetGlobal("Area", this);
205 turnScript.invoke();
207 pythonSetGlobal("Area", this);
208 player->turn();
210 BOOST_FOREACH(Character* c, characters) {
211 pythonSetGlobal("Area", this);
212 c->turn();
215 view->turn();
218 // Python API.
219 void Area::setColorOverlay(int r, int g, int b, int a)
221 using namespace Gosu;
223 if (0 <= r && r < 256 &&
224 0 <= g && g < 256 &&
225 0 <= b && b < 256 &&
226 0 <= a && a < 256) {
227 Color::Channel ac = (Color::Channel)a;
228 Color::Channel rc = (Color::Channel)r;
229 Color::Channel gc = (Color::Channel)g;
230 Color::Channel bc = (Color::Channel)b;
231 colorOverlay = Color(ac, rc, gc, bc);
232 redraw = true;
234 else {
235 PyErr_Format(PyExc_ValueError,
236 "Area::color_overlay() arguments must be "
237 "between 0 and 255");
243 const Tile* Area::getTile(int x, int y, int z) const
245 if (loopX)
246 x = wrap(0, x, dim.x);
247 if (loopY)
248 y = wrap(0, y, dim.y);
249 if (inBounds(x, y, z))
250 return &map[z][y][x];
251 else
252 return NULL;
255 const Tile* Area::getTile(int x, int y, double z) const
257 return getTile(x, y, depthIndex(z));
260 const Tile* Area::getTile(icoord phys) const
262 return getTile(phys.x, phys.y, phys.z);
265 const Tile* Area::getTile(vicoord virt) const
267 return getTile(virt2phys(virt));
270 const Tile* Area::getTile(rcoord virt) const
272 return getTile(virt2phys(virt));
275 Tile* Area::getTile(int x, int y, int z)
277 if (loopX)
278 x = wrap(0, x, dim.x);
279 if (loopY)
280 y = wrap(0, y, dim.y);
281 if (inBounds(x, y, z))
282 return &map[z][y][x];
283 else
284 return NULL;
287 Tile* Area::getTile(int x, int y, double z)
289 return getTile(x, y, depthIndex(z));
292 Tile* Area::getTile(icoord phys)
294 return getTile(phys.x, phys.y, phys.z);
297 Tile* Area::getTile(vicoord virt)
299 return getTile(virt2phys(virt));
302 Tile* Area::getTile(rcoord virt)
304 return getTile(virt2phys(virt));
307 TileSet* Area::getTileSet(const std::string& imagePath)
309 std::map<std::string, TileSet>::iterator it;
310 it = tileSets.find(imagePath);
311 if (it == tileSets.end()) {
312 Log::err("Area", "tileset " + imagePath + " not found");
313 return NULL;
315 return &tileSets[imagePath];
319 ivec3 Area::getDimensions() const
321 return dim;
324 ivec2 Area::getTileDimensions() const
326 return tileDim;
329 icube Area::visibleTileBounds() const
331 rvec2 screen = view->getVirtRes();
332 rvec2 off = view->getMapOffset();
334 int x1 = (int)floor(off.x / tileDim.x);
335 int y1 = (int)floor(off.y / tileDim.y);
336 int x2 = (int)ceil((screen.x + off.x) / tileDim.x);
337 int y2 = (int)ceil((screen.y + off.y) / tileDim.y);
339 return icube(x1, y1, 0, x2, y2, dim.z);
342 icube Area::visibleTiles() const
344 icube cube = visibleTileBounds();
345 if (!loopX) {
346 cube.x1 = std::max(cube.x1, 0);
347 cube.x2 = std::min(cube.x2, dim.x);
349 if (!loopY) {
350 cube.y1 = std::max(cube.y1, 0);
351 cube.y2 = std::min(cube.y2, dim.y);
353 return cube;
356 bool Area::inBounds(int x, int y, int z) const
358 return ((loopX || (0 <= x && x < dim.x)) &&
359 (loopY || (0 <= y && y < dim.y)) &&
360 0 <= z && z < dim.z);
363 bool Area::inBounds(int x, int y, double z) const
365 return inBounds(x, y, depthIndex(z));
368 bool Area::inBounds(icoord phys) const
370 return inBounds(phys.x, phys.y, phys.z);
373 bool Area::inBounds(vicoord virt) const
375 return inBounds(virt2phys(virt));
378 bool Area::inBounds(rcoord virt) const
380 return inBounds(virt2phys(virt));
383 bool Area::inBounds(Entity* ent) const
385 return inBounds(ent->getPixelCoord());
390 bool Area::loopsInX() const
392 return loopX;
395 bool Area::loopsInY() const
397 return loopY;
400 const std::string Area::getDescriptor() const
402 return descriptor;
405 Entity* Area::spawnNPC(const std::string& descriptor,
406 int x, int y, double z, const std::string& phase)
408 Character* c = new NPC();
409 if (!c->init(descriptor)) {
410 // Error logged.
411 delete c;
412 return NULL;
414 c->setArea(this);
415 if (!c->setPhase(phase)) {
416 // Error logged.
417 delete c;
418 return NULL;
420 c->setTileCoords(x, y, z);
421 insert(c);
422 return c;
425 Entity* Area::spawnOverlay(const std::string& descriptor,
426 int x, int y, double z, const std::string& phase)
428 Overlay* o = new Overlay();
429 if (!o->init(descriptor)) {
430 // Error logged.
431 delete o;
432 return NULL;
434 o->setArea(this);
435 if (!o->setPhase(phase)) {
436 // Error logged.
437 delete o;
438 return NULL;
440 o->setTileCoords(x, y, z);
441 // XXX: o->leaveTile(); // Overlays don't consume tiles.
443 insert(o);
444 return o;
447 void Area::insert(Character* c)
449 characters.insert(c);
452 void Area::insert(Overlay* o)
454 overlays.insert(o);
457 void Area::erase(Character* c)
459 characters.erase(c);
462 void Area::erase(Overlay* o)
464 overlays.erase(o);
469 vicoord Area::phys2virt_vi(icoord phys) const
471 return vicoord(phys.x, phys.y, indexDepth(phys.z));
474 rcoord Area::phys2virt_r(icoord phys) const
476 return rcoord(
477 (double)phys.x * tileDim.x,
478 (double)phys.y * tileDim.y,
479 indexDepth(phys.z)
483 icoord Area::virt2phys(vicoord virt) const
485 return icoord(virt.x, virt.y, depthIndex(virt.z));
488 icoord Area::virt2phys(rcoord virt) const
490 return icoord(
491 (int)(virt.x / tileDim.x),
492 (int)(virt.y / tileDim.y),
493 depthIndex(virt.z)
497 rcoord Area::virt2virt(vicoord virt) const
499 return rcoord(
500 (double)virt.x * tileDim.x,
501 (double)virt.y * tileDim.y,
502 virt.z
506 vicoord Area::virt2virt(rcoord virt) const
508 return vicoord(
509 (int)virt.x / tileDim.x,
510 (int)virt.y / tileDim.y,
511 virt.z
516 int Area::depthIndex(double depth) const
518 return depth2idx.find(depth)->second;
521 double Area::indexDepth(int idx) const
523 return idx2depth[idx];
528 void Area::runLoadScripts()
530 World* world = World::instance();
531 world->runAreaLoadScript(this);
533 pythonSetGlobal("Area", this);
534 loadScript.invoke();
537 void Area::drawTiles()
539 icube tiles = visibleTiles();
540 for (int z = tiles.z1; z < tiles.z2; z++) {
541 double depth = idx2depth[z];
542 for (int y = tiles.y1; y < tiles.y2; y++) {
543 for (int x = tiles.x1; x < tiles.x2; x++) {
544 Tile* tile = getTile(x, y, z);
545 // We are certain the Tile exists.
546 drawTile(*tile, x, y, depth);
552 void Area::drawTile(Tile& tile, int x, int y, double depth)
554 TileType* type = (TileType*)tile.parent;
555 if (type) {
556 time_t now = World::instance()->time();
557 const Image* img = type->anim.frame(now);
558 if (img)
559 img->draw((double)x*img->width(),
560 (double)y*img->height(), depth);
564 void Area::drawEntities()
566 BOOST_FOREACH(Character* c, characters) {
567 c->draw();
569 BOOST_FOREACH(Overlay* o, overlays) {
570 o->draw();
572 player->draw();
575 void Area::drawColorOverlay()
577 if (colorOverlay.alpha() != 0) {
578 GameWindow& window = GameWindow::instance();
579 Gosu::Color c = colorOverlay;
580 int x = window.width();
581 int y = window.height();
582 window.graphics().drawQuad(
583 0, 0, c,
584 x, 0, c,
585 x, y, c,
586 0, y, c,
592 boost::python::tuple Area::pyGetDimensions()
594 using namespace boost::python;
596 list zs;
597 BOOST_FOREACH(double dep, idx2depth)
598 zs.append(dep);
599 return make_tuple(dim.x, dim.y, zs);
602 void exportArea()
604 using namespace boost::python;
606 class_<Area>("Area", no_init)
607 .add_property("descriptor", &Area::getDescriptor)
608 .add_property("dimensions", &Area::pyGetDimensions)
609 .def("redraw", &Area::requestRedraw)
610 .def("tileset", &Area::getTileSet,
611 return_value_policy<reference_existing_object>())
612 .def("tile",
613 static_cast<Tile* (Area::*) (int, int, double)>
614 (&Area::getTile),
615 return_value_policy<reference_existing_object>())
616 .def("in_bounds",
617 static_cast<bool (Area::*) (int, int, double) const>
618 (&Area::inBounds))
619 .def("color_overlay", &Area::setColorOverlay)
620 .def("new_npc", &Area::spawnNPC,
621 return_value_policy<reference_existing_object>())
622 .def("new_overlay", &Area::spawnOverlay,
623 return_value_policy<reference_existing_object>())
624 .def_readwrite("on_focus", &Area::focusScript)
625 .def_readwrite("on_tick", &Area::tickScript)
626 .def_readwrite("on_turn", &Area::turnScript)