1 /*********************************
2 ** Tsunagari Tile Engine **
4 ** Copyright 2011-2012 OmegaSDG **
5 *********************************/
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
29 #include <stdlib.h> // for exit(1) on fatal
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>
46 #include "resourcer.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
60 static T
wrap(T min
, T value
, T max
)
67 Area::Area(Viewport
* view
,
70 const std::string
& descriptor
)
74 colorOverlay(0, 0, 0, 0),
77 loopX(false), loopY(false),
80 descriptor(descriptor
)
102 music
->setIntro(musicIntro
.get());
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
)
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));
144 bool Area::needsRedraw() const
148 if (player
->needsRedraw())
151 BOOST_FOREACH(Character
* c
, characters
)
152 if (c
->needsRedraw())
154 BOOST_FOREACH(Overlay
* o
, overlays
)
155 if (o
->needsRedraw())
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())
173 void Area::requestRedraw()
178 void Area::tick(unsigned long dt
)
180 pythonSetGlobal("Area", this);
183 BOOST_FOREACH(Overlay
* o
, overlays
) {
184 pythonSetGlobal("Area", this);
188 if (conf
.moveMode
!= TURN
) {
189 pythonSetGlobal("Area", this);
192 BOOST_FOREACH(Character
* c
, characters
) {
193 pythonSetGlobal("Area", this);
204 pythonSetGlobal("Area", this);
207 pythonSetGlobal("Area", this);
210 BOOST_FOREACH(Character
* c
, characters
) {
211 pythonSetGlobal("Area", this);
219 void Area::setColorOverlay(int r
, int g
, int b
, int a
)
221 using namespace Gosu
;
223 if (0 <= r
&& r
< 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
);
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
246 x
= wrap(0, x
, dim
.x
);
248 y
= wrap(0, y
, dim
.y
);
249 if (inBounds(x
, y
, z
))
250 return &map
[z
][y
][x
];
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
)
278 x
= wrap(0, x
, dim
.x
);
280 y
= wrap(0, y
, dim
.y
);
281 if (inBounds(x
, y
, z
))
282 return &map
[z
][y
][x
];
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");
315 return &tileSets
[imagePath
];
319 ivec3
Area::getDimensions() const
324 ivec2
Area::getTileDimensions() const
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();
346 cube
.x1
= std::max(cube
.x1
, 0);
347 cube
.x2
= std::min(cube
.x2
, dim
.x
);
350 cube
.y1
= std::max(cube
.y1
, 0);
351 cube
.y2
= std::min(cube
.y2
, dim
.y
);
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
395 bool Area::loopsInY() const
400 const std::string
Area::getDescriptor() const
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
)) {
415 if (!c
->setPhase(phase
)) {
420 c
->setTileCoords(x
, y
, z
);
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
)) {
435 if (!o
->setPhase(phase
)) {
440 o
->setTileCoords(x
, y
, z
);
441 // XXX: o->leaveTile(); // Overlays don't consume tiles.
447 void Area::insert(Character
* c
)
449 characters
.insert(c
);
452 void Area::insert(Overlay
* o
)
457 void Area::erase(Character
* c
)
462 void Area::erase(Overlay
* 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
477 (double)phys
.x
* tileDim
.x
,
478 (double)phys
.y
* tileDim
.y
,
483 icoord
Area::virt2phys(vicoord virt
) const
485 return icoord(virt
.x
, virt
.y
, depthIndex(virt
.z
));
488 icoord
Area::virt2phys(rcoord virt
) const
491 (int)(virt
.x
/ tileDim
.x
),
492 (int)(virt
.y
/ tileDim
.y
),
497 rcoord
Area::virt2virt(vicoord virt
) const
500 (double)virt
.x
* tileDim
.x
,
501 (double)virt
.y
* tileDim
.y
,
506 vicoord
Area::virt2virt(rcoord virt
) const
509 (int)virt
.x
/ tileDim
.x
,
510 (int)virt
.y
/ tileDim
.y
,
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);
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
;
556 time_t now
= World::instance()->time();
557 const Image
* img
= type
->anim
.frame(now
);
559 img
->draw((double)x
*img
->width(),
560 (double)y
*img
->height(), depth
);
564 void Area::drawEntities()
566 BOOST_FOREACH(Character
* c
, characters
) {
569 BOOST_FOREACH(Overlay
* o
, overlays
) {
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(
592 boost::python::tuple
Area::pyGetDimensions()
594 using namespace boost::python
;
597 BOOST_FOREACH(double dep
, idx2depth
)
599 return make_tuple(dim
.x
, dim
.y
, zs
);
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
>())
613 static_cast<Tile
* (Area::*) (int, int, double)>
615 return_value_policy
<reference_existing_object
>())
617 static_cast<bool (Area::*) (int, int, double) const>
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
)