1 /*********************************
2 ** Tsunagari Tile Engine **
4 ** Copyright 2011-2012 OmegaSDG **
5 *********************************/
7 // "OmegaSDG" is defined as Michael D. Reiley and Paul Merrill.
10 // Permission is hereby granted, free of charge, to any person obtaining a copy
11 // of this software and associated documentation files (the "Software"), to
12 // deal in the Software without restriction, including without limitation the
13 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
14 // sell copies of the Software, and to permit persons to whom the Software is
15 // furnished to do so, subject to the following conditions:
17 // The above copyright notice and this permission notice shall be included in
18 // all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
29 #include <Gosu/Image.hpp>
30 #include <Gosu/Utility.hpp>
41 #define ASSERT(x) if (!(x)) { return false; }
43 static World
* globalWorld
= NULL
;
45 World
* World::instance()
51 : total(0), redraw(false), userPaused(false), paused(0)
54 lastTime
= GameWindow::instance().time();
55 pythonSetGlobal("World", this);
64 ASSERT(processDescriptor()); // Try to load in descriptor.
65 ASSERT(pauseInfo
= Reader::getImage("resource/pause_overlay.png"));
67 ASSERT(player
.init(playerEntity
, playerPhase
));
68 pythonSetGlobal("Player", (Entity
*)&player
);
70 view
.reset(new Viewport(viewportSz
));
71 view
->trackEntity(&player
);
76 Area
* area
= getArea(startArea
);
78 Log::fatal("World", "failed to load initial Area");
81 focusArea(area
, startCoords
);
86 const std::string
& World::getName() const
91 time_t World::time() const
96 void World::buttonDown(const Gosu::Button btn
)
100 userPaused
= !userPaused
;
101 setPaused(userPaused
);
105 if (!paused
&& keyStates
.empty()) {
106 area
->buttonDown(btn
);
108 keydownScript
->invoke();
114 void World::buttonUp(const Gosu::Button btn
)
120 if (!paused
&& keyStates
.empty()) {
123 keyupScript
->invoke();
133 GameWindow
& window
= GameWindow::instance();
134 Gosu::Graphics
& graphics
= window
.graphics();
136 int clips
= pushLetterbox();
137 graphics
.pushTransform(getTransform());
141 graphics
.popTransform();
145 unsigned ww
= graphics
.width();
146 unsigned wh
= graphics
.height();
147 Gosu::Color
darken(127, 0, 0, 0);
148 double top
= std::numeric_limits
<double>::max();
150 drawRect(0, ww
, 0, wh
, darken
, top
);
153 unsigned iw
= pauseInfo
->width();
154 unsigned ih
= pauseInfo
->height();
156 pauseInfo
->draw(ww
/2 - iw
/2, wh
/2 - ih
/2, top
);
163 bool World::needsRedraw() const
167 if (!paused
&& area
->needsRedraw())
172 void World::update(time_t now
)
174 time_t dt
= calculateDt(now
);
181 void World::tick(unsigned long dt
)
189 if (conf
.moveMode
== TURN
) {
195 Area
* World::getArea(const std::string
& filename
)
197 AreaMap::iterator entry
= areas
.find(filename
);
198 if (entry
!= areas
.end())
199 return entry
->second
;
201 Area
* newArea
= new AreaTMX(view
.get(), &player
, filename
);
203 if (!newArea
->init())
205 areas
[filename
] = newArea
;
209 Area
* World::getFocusedArea()
214 void World::focusArea(Area
* area
, int x
, int y
, double z
)
216 focusArea(area
, vicoord(x
, y
, z
));
219 void World::focusArea(Area
* area
, vicoord playerPos
)
222 player
.setArea(area
);
223 player
.setTileCoords(playerPos
);
228 void World::setPaused(bool b
)
231 Log::err("World", "trying to unpause, but not paused");
239 paused
+= b
? 1 : -1;
242 Music::setPaused(true);
244 Music::setPaused(false);
246 // If finally unpausing.
251 void World::storeKeys()
253 keyStates
.push(BitRecord::fromGosuInput());
256 void World::restoreKeys()
258 BitRecord now
= BitRecord::fromGosuInput();
259 BitRecord then
= keyStates
.top();
260 std::vector
<size_t> diffs
= now
.diff(then
);
264 BOOST_FOREACH(size_t id
, diffs
) {
265 Gosu::Button
btn((unsigned)id
);
273 void World::runAreaLoadScript(Area
* area
)
275 pythonSetGlobal("Area", area
);
277 areaLoadScript
->invoke();
280 time_t World::calculateDt(time_t now
)
282 time_t dt
= now
- lastTime
;
287 int World::pushLetterbox()
289 GameWindow
& w
= GameWindow::instance();
290 Gosu::Graphics
& g
= w
.graphics();
292 // Aspect ratio correction.
293 rvec2 sz
= view
->getPhysRes();
294 rvec2 lb
= -1 * view
->getLetterboxOffset();
296 g
.beginClipping(lb
.x
, lb
.y
, sz
.x
- 2 * lb
.x
, sz
.y
- 2 * lb
.y
);
300 rvec2 scale
= view
->getScale();
301 rvec2 virtScroll
= view
->getMapOffset();
302 rvec2 padding
= view
->getLetterboxOffset();
304 rvec2 physScroll
= -1 * virtScroll
* scale
+ padding
;
306 bool loopX
= area
->loopsInX();
307 bool loopY
= area
->loopsInY();
309 if (!loopX
&& physScroll
.x
> 0) {
310 // Boxes on left-right.
311 g
.beginClipping(physScroll
.x
, 0, sz
.x
- 2 * physScroll
.x
, sz
.x
);
314 if (!loopY
&& physScroll
.y
> 0) {
315 // Boxes on top-bottom.
316 g
.beginClipping(0, physScroll
.y
, sz
.x
, sz
.y
- 2 * physScroll
.y
);
323 void World::popLetterbox(int clips
)
325 GameWindow
& w
= GameWindow::instance();
326 for (; clips
; clips
--)
327 w
.graphics().endClipping();
330 void World::drawRect(double x1
, double x2
, double y1
, double y2
,
331 Gosu::Color c
, double z
)
333 GameWindow
& window
= GameWindow::instance();
334 window
.graphics().drawQuad(
343 Gosu::Transform
World::getTransform()
345 rvec2 scale
= view
->getScale();
346 rvec2 scroll
= view
->getMapOffset();
347 rvec2 padding
= view
->getLetterboxOffset();
348 Gosu::Transform t
= { {
352 scale
.x
* -scroll
.x
- padding
.x
,
353 scale
.y
* -scroll
.y
- padding
.y
, 0, 1
358 bool World::processDescriptor()
363 ASSERT(doc
= Reader::getXMLDoc("world.conf", "dtd/world.dtd"));
364 ASSERT(root
= doc
->root()); // <world>
366 for (XMLNode child
= root
.childrenNode(); child
; child
= child
.next()) {
367 if (child
.is("info")) {
368 ASSERT(processInfo(child
));
370 else if (child
.is("init")) {
371 ASSERT(processInit(child
));
373 else if (child
.is("script")) {
374 ASSERT(processScript(child
));
376 else if (child
.is("input")) {
377 ASSERT(processInput(child
));
381 if (conf
.moveMode
== TURN
&&
382 (conf
.persistInit
== 0 || conf
.persistCons
== 0)) {
383 Log::fatal("world.conf", "\"input->persist\" option required in \"turn\" mode");
390 bool World::processInfo(XMLNode node
)
392 for (node
= node
.childrenNode(); node
; node
= node
.next()) {
393 if (node
.is("name")) {
394 name
= node
.content();
395 GameWindow::instance().setCaption(Gosu::widen(name
));
396 } else if (node
.is("author")) {
397 author
= node
.content();
398 } else if (node
.is("version")) {
399 version
= atof(node
.content().c_str());
405 bool World::processInit(XMLNode node
)
407 for (node
= node
.childrenNode(); node
; node
= node
.next()) {
408 if (node
.is("area")) {
409 startArea
= node
.content();
411 else if (node
.is("player")) {
412 playerEntity
= node
.attr("file");
413 playerPhase
= node
.attr("phase");
415 else if (node
.is("mode")) {
416 std::string str
= node
.content();
418 conf
.moveMode
= TURN
;
419 else if (str
== "tile")
420 conf
.moveMode
= TILE
;
421 else if (str
== "notile")
422 conf
.moveMode
= NOTILE
;
424 else if (node
.is("coords")) {
425 if (!node
.intAttr("x", &startCoords
.x
) ||
426 !node
.intAttr("y", &startCoords
.y
) ||
427 !node
.doubleAttr("z", &startCoords
.z
))
430 else if (node
.is("viewport")) {
431 if (!node
.intAttr("width", &viewportSz
.x
) ||
432 !node
.intAttr("height", &viewportSz
.y
))
439 bool World::processScript(XMLNode node
)
441 for (node
= node
.childrenNode(); node
; node
= node
.next()) {
442 std::string filename
= node
.content();
443 ScriptInst
script(filename
);
445 if (node
.is("on_init")) {
446 if (!script
.validate())
449 } else if (node
.is("on_area_init")) {
450 if (!script
.validate())
452 areaLoadScript
= script
;
458 bool World::processInput(XMLNode node
)
460 for (node
= node
.childrenNode(); node
; node
= node
.next()) {
461 if (node
.is("persist")) {
462 if (!node
.intAttr("init", &conf
.persistInit
) ||
463 !node
.intAttr("cons", &conf
.persistCons
))
472 using namespace boost::python
;
474 class_
<World
> ("World", no_init
)
475 .def("area", &World::getArea
,
476 return_value_policy
<reference_existing_object
>())
478 static_cast<void (World::*) (Area
*,int,int,double)>
480 .def_readwrite("on_key_down", &World::keydownScript
)
481 .def_readwrite("on_key_up", &World::keyupScript
)