1 /***************************************
2 ** Tsunagari Tile Engine **
4 ** Copyright 2011-2013 PariahSoft LLC **
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
31 #include <Gosu/Graphics.hpp>
32 #include <Gosu/Math.hpp>
33 #include <Gosu/Timing.hpp>
36 #include "client-conf.h"
38 #include "formatter.h"
45 #include "python-bindings-template.cpp"
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
,
69 const std::string
& descriptor
)
72 colorOverlay(0, 0, 0, 0),
75 loopX(false), loopY(false),
78 descriptor(descriptor
)
100 World::instance()->getMusic()->setIntro(musicIntro
);
102 World::instance()->getMusic()->setLoop(musicLoop
);
104 pythonSetGlobal("Area", this);
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
)
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));
143 bool Area::needsRedraw() const
147 if (player
->needsRedraw())
150 for (CharacterSet::iterator it
= characters
.begin(); it
!= characters
.end(); it
++) {
152 if (c
->needsRedraw())
155 for (OverlaySet::iterator it
= overlays
.begin(); it
!= overlays
.end(); it
++) {
157 if (o
->needsRedraw())
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())
176 void Area::requestRedraw()
181 void Area::tick(unsigned long dt
)
183 pythonSetGlobal("Area", this);
185 tickScript
->invoke();
187 for (OverlaySet::iterator it
= overlays
.begin(); it
!= overlays
.end(); it
++) {
189 pythonSetGlobal("Area", this);
193 if (conf
.moveMode
!= TURN
) {
194 pythonSetGlobal("Area", this);
197 for (CharacterSet::iterator it
= characters
.begin(); it
!= characters
.end(); it
++) {
199 pythonSetGlobal("Area", this);
205 World::instance()->getMusic()->tick();
210 pythonSetGlobal("Area", this);
212 turnScript
->invoke();
214 pythonSetGlobal("Area", this);
217 for (CharacterSet::iterator it
= characters
.begin(); it
!= characters
.end(); it
++) {
219 pythonSetGlobal("Area", this);
227 void Area::setColorOverlay(int r
, int g
, int b
, int a
)
229 using namespace Gosu
;
231 if (0 <= r
&& r
< 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
);
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
254 x
= wrap(0, x
, dim
.x
);
256 y
= wrap(0, y
, dim
.y
);
257 if (inBounds(x
, y
, z
))
258 return &map
[z
][y
][x
];
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
)
286 x
= wrap(0, x
, dim
.x
);
288 y
= wrap(0, y
, dim
.y
);
289 if (inBounds(x
, y
, z
))
290 return &map
[z
][y
][x
];
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");
323 return &tileSets
[imagePath
];
327 ivec3
Area::getDimensions() const
332 ivec2
Area::getTileDimensions() const
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();
359 cube
.x1
= std::max(cube
.x1
, 0);
360 cube
.x2
= std::min(cube
.x2
, dim
.x
);
363 cube
.y1
= std::max(cube
.y1
, 0);
364 cube
.y2
= std::min(cube
.y2
, dim
.y
);
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
408 bool Area::loopsInY() const
413 const std::string
Area::getDescriptor() const
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
)) {
428 if (!c
->setPhase(phase
)) {
433 c
->setTileCoords(x
, y
, z
);
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
)) {
448 if (!o
->setPhase(phase
)) {
453 o
->setTileCoords(x
, y
, z
);
454 // XXX: o->leaveTile(); // Overlays don't consume tiles.
460 void Area::insert(Character
* c
)
462 characters
.insert(c
);
465 void Area::insert(Overlay
* o
)
470 void Area::erase(Character
* c
)
475 void Area::erase(Overlay
* 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
490 (double)phys
.x
* tileDim
.x
,
491 (double)phys
.y
* tileDim
.y
,
496 icoord
Area::virt2phys(vicoord virt
) const
498 return icoord(virt
.x
, virt
.y
, depthIndex(virt
.z
));
501 icoord
Area::virt2phys(rcoord virt
) const
504 (int)(virt
.x
/ tileDim
.x
),
505 (int)(virt
.y
/ tileDim
.y
),
510 rcoord
Area::virt2virt(vicoord virt
) const
513 (double)virt
.x
* tileDim
.x
,
514 (double)virt
.y
* tileDim
.y
,
519 vicoord
Area::virt2virt(rcoord virt
) const
522 (int)virt
.x
/ tileDim
.x
,
523 (int)virt
.y
/ tileDim
.y
,
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
);
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);
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
;
577 time_t now
= World::instance()->time();
578 const Image
* img
= type
->anim
.frame(now
);
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
++) {
596 for (OverlaySet::iterator it
= overlays
.begin(); it
!= overlays
.end(); it
++) {
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(
620 /* FIXME: Don't expose boost::python::tuple to the header file. */
621 //boost::python::tuple Area::pyGetDimensions()
623 // using namespace boost::python;
626 // BOOST_FOREACH(double dep, idx2depth)
628 // return make_tuple(dim.x, dim.y, zs);
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
>())
642 static_cast<Tile
* (Area::*) (int, int, double)>
644 return_value_policy
<reference_existing_object
>())
646 static_cast<bool (Area::*) (int, int, double) const>
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)