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
32 #include <boost/shared_ptr.hpp>
33 #include <Gosu/Graphics.hpp>
34 #include <Gosu/Math.hpp>
35 #include <Gosu/Timing.hpp>
38 #include "client-conf.h"
40 #include "formatter.h"
47 #include "python-bindings-template.cpp"
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
62 static T
wrap(T min
, T value
, T max
)
69 Area::Area(Viewport
* view
,
71 const std::string
& descriptor
)
74 colorOverlay(0, 0, 0, 0),
77 loopX(false), loopY(false),
80 descriptor(descriptor
)
102 Music::setIntro(musicIntro
);
104 Music::setLoop(musicLoop
);
106 pythonSetGlobal("Area", this);
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
)
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));
145 bool Area::needsRedraw() const
149 if (player
->needsRedraw())
152 for (CharacterSet::iterator it
= characters
.begin(); it
!= characters
.end(); it
++) {
154 if (c
->needsRedraw())
157 for (OverlaySet::iterator it
= overlays
.begin(); it
!= overlays
.end(); it
++) {
159 if (o
->needsRedraw())
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())
178 void Area::requestRedraw()
183 void Area::tick(unsigned long dt
)
185 pythonSetGlobal("Area", this);
187 tickScript
->invoke();
189 for (OverlaySet::iterator it
= overlays
.begin(); it
!= overlays
.end(); it
++) {
191 pythonSetGlobal("Area", this);
195 if (conf
.moveMode
!= TURN
) {
196 pythonSetGlobal("Area", this);
199 for (CharacterSet::iterator it
= characters
.begin(); it
!= characters
.end(); it
++) {
201 pythonSetGlobal("Area", this);
212 pythonSetGlobal("Area", this);
214 turnScript
->invoke();
216 pythonSetGlobal("Area", this);
219 for (CharacterSet::iterator it
= characters
.begin(); it
!= characters
.end(); it
++) {
221 pythonSetGlobal("Area", this);
229 void Area::setColorOverlay(int r
, int g
, int b
, int a
)
231 using namespace Gosu
;
233 if (0 <= r
&& r
< 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
);
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
256 x
= wrap(0, x
, dim
.x
);
258 y
= wrap(0, y
, dim
.y
);
259 if (inBounds(x
, y
, z
))
260 return &map
[z
][y
][x
];
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
)
288 x
= wrap(0, x
, dim
.x
);
290 y
= wrap(0, y
, dim
.y
);
291 if (inBounds(x
, y
, z
))
292 return &map
[z
][y
][x
];
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");
325 return &tileSets
[imagePath
];
329 ivec3
Area::getDimensions() const
334 ivec2
Area::getTileDimensions() const
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();
361 cube
.x1
= std::max(cube
.x1
, 0);
362 cube
.x2
= std::min(cube
.x2
, dim
.x
);
365 cube
.y1
= std::max(cube
.y1
, 0);
366 cube
.y2
= std::min(cube
.y2
, dim
.y
);
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
410 bool Area::loopsInY() const
415 const std::string
Area::getDescriptor() const
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
)) {
430 if (!c
->setPhase(phase
)) {
435 c
->setTileCoords(x
, y
, z
);
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
)) {
450 if (!o
->setPhase(phase
)) {
455 o
->setTileCoords(x
, y
, z
);
456 // XXX: o->leaveTile(); // Overlays don't consume tiles.
462 void Area::insert(Character
* c
)
464 characters
.insert(c
);
467 void Area::insert(Overlay
* o
)
472 void Area::erase(Character
* c
)
477 void Area::erase(Overlay
* 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
492 (double)phys
.x
* tileDim
.x
,
493 (double)phys
.y
* tileDim
.y
,
498 icoord
Area::virt2phys(vicoord virt
) const
500 return icoord(virt
.x
, virt
.y
, depthIndex(virt
.z
));
503 icoord
Area::virt2phys(rcoord virt
) const
506 (int)(virt
.x
/ tileDim
.x
),
507 (int)(virt
.y
/ tileDim
.y
),
512 rcoord
Area::virt2virt(vicoord virt
) const
515 (double)virt
.x
* tileDim
.x
,
516 (double)virt
.y
* tileDim
.y
,
521 vicoord
Area::virt2virt(rcoord virt
) const
524 (int)virt
.x
/ tileDim
.x
,
525 (int)virt
.y
/ tileDim
.y
,
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
);
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);
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
;
581 time_t now
= World::instance()->time();
582 const Image
* img
= type
->anim
.frame(now
);
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
++) {
600 for (OverlaySet::iterator it
= overlays
.begin(); it
!= overlays
.end(); it
++) {
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(
624 /* FIXME: Don't expose boost::python::tuple to the header file. */
625 //boost::python::tuple Area::pyGetDimensions()
627 // using namespace boost::python;
630 // BOOST_FOREACH(double dep, idx2depth)
632 // return make_tuple(dim.x, dim.y, zs);
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
>())
646 static_cast<Tile
* (Area::*) (int, int, double)>
648 return_value_policy
<reference_existing_object
>())
650 static_cast<bool (Area::*) (int, int, double) const>
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)