FIX: python import
[Tsunagari.git] / src / world.cpp
blobdda785f878cdbc4a319d44bc47afa19cd580e192
1 /*********************************
2 ** Tsunagari Tile Engine **
3 ** world.cpp **
4 ** Copyright 2011-2012 OmegaSDG **
5 *********************************/
7 // "OmegaSDG" is defined as Michael D. Reiley and Paul Merrill.
9 // **********
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
26 // IN THE SOFTWARE.
27 // **********
29 #include <Gosu/Image.hpp>
30 #include <Gosu/Utility.hpp>
32 #include "area-tmx.h"
33 #include "log.h"
34 #include "music.h"
35 #include "python.h"
36 #include "timeout.h"
37 #include "window.h"
38 #include "world.h"
39 #include "xml.h"
41 #define ASSERT(x) if (!(x)) { return false; }
43 static World* globalWorld = NULL;
45 World* World::instance()
47 return globalWorld;
50 World::World()
51 : total(0), redraw(false), userPaused(false), paused(0)
53 globalWorld = this;
54 lastTime = GameWindow::instance().time();
55 pythonSetGlobal("World", this);
58 World::~World()
62 bool World::init()
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);
73 if (loadScript)
74 loadScript->invoke();
76 Area* area = getArea(startArea);
77 if (area == NULL) {
78 Log::fatal("World", "failed to load initial Area");
79 return false;
81 focusArea(area, startCoords);
83 return true;
86 const std::string& World::getName() const
88 return name;
91 time_t World::time() const
93 return total;
96 void World::buttonDown(const Gosu::Button btn)
98 switch (btn.id()) {
99 case Gosu::kbEscape:
100 userPaused = !userPaused;
101 setPaused(userPaused);
102 redraw = true;
103 break;
104 default:
105 if (!paused && keyStates.empty()) {
106 area->buttonDown(btn);
107 if (keydownScript)
108 keydownScript->invoke();
110 break;
114 void World::buttonUp(const Gosu::Button btn)
116 switch (btn.id()) {
117 case Gosu::kbEscape:
118 break;
119 default:
120 if (!paused && keyStates.empty()) {
121 area->buttonUp(btn);
122 if (keyupScript)
123 keyupScript->invoke();
125 break;
129 void World::draw()
131 redraw = false;
133 GameWindow& window = GameWindow::instance();
134 Gosu::Graphics& graphics = window.graphics();
136 int clips = pushLetterbox();
137 graphics.pushTransform(getTransform());
139 area->draw();
141 graphics.popTransform();
142 popLetterbox(clips);
144 if (paused) {
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);
152 if (userPaused) {
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
165 if (redraw)
166 return true;
167 if (!paused && area->needsRedraw())
168 return true;
169 return false;
172 void World::update(time_t now)
174 time_t dt = calculateDt(now);
175 if (!paused) {
176 total += dt;
177 tick(dt);
181 void World::tick(unsigned long dt)
183 updateTimeouts();
184 area->tick(dt);
187 void World::turn()
189 if (conf.moveMode == TURN) {
190 updateTimeouts();
191 area->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())
204 newArea = NULL;
205 areas[filename] = newArea;
206 return newArea;
209 Area* World::getFocusedArea()
211 return area;
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)
221 this->area = area;
222 player.setArea(area);
223 player.setTileCoords(playerPos);
224 view->setArea(area);
225 area->focus();
228 void World::setPaused(bool b)
230 if (!paused && !b) {
231 Log::err("World", "trying to unpause, but not paused");
232 return;
235 // If just pausing.
236 if (!paused)
237 storeKeys();
239 paused += b ? 1 : -1;
241 if (paused)
242 Music::setPaused(true);
243 else
244 Music::setPaused(false);
246 // If finally unpausing.
247 if (!paused)
248 restoreKeys();
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);
262 keyStates.pop();
264 BOOST_FOREACH(size_t id, diffs) {
265 Gosu::Button btn((unsigned)id);
266 if (now[id])
267 buttonDown(btn);
268 else
269 buttonUp(btn);
273 void World::runAreaLoadScript(Area* area)
275 pythonSetGlobal("Area", area);
276 if (areaLoadScript)
277 areaLoadScript->invoke();
280 time_t World::calculateDt(time_t now)
282 time_t dt = now - lastTime;
283 lastTime = now;
284 return dt;
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);
297 int clips = 1;
299 // Map bounds.
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);
312 clips++;
314 if (!loopY && physScroll.y > 0) {
315 // Boxes on top-bottom.
316 g.beginClipping(0, physScroll.y, sz.x, sz.y - 2 * physScroll.y);
317 clips++;
320 return clips;
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(
335 x1, y1, c,
336 x2, y1, c,
337 x2, y2, c,
338 x1, y2, c,
343 Gosu::Transform World::getTransform()
345 rvec2 scale = view->getScale();
346 rvec2 scroll = view->getMapOffset();
347 rvec2 padding = view->getLetterboxOffset();
348 Gosu::Transform t = { {
349 scale.x, 0, 0, 0,
350 0, scale.y, 0, 0,
351 0, 0, 1, 0,
352 scale.x * -scroll.x - padding.x,
353 scale.y * -scroll.y - padding.y, 0, 1
354 } };
355 return t;
358 bool World::processDescriptor()
360 XMLRef doc;
361 XMLNode root;
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");
384 return false;
387 return true;
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());
402 return true;
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();
417 if (str == "turn")
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))
428 return false;
430 else if (node.is("viewport")) {
431 if (!node.intAttr("width", &viewportSz.x) ||
432 !node.intAttr("height", &viewportSz.y))
433 return false;
436 return true;
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())
447 return false;
448 loadScript = script;
449 } else if (node.is("on_area_init")) {
450 if (!script.validate())
451 return false;
452 areaLoadScript = script;
455 return true;
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))
464 return false;
467 return true;
470 void exportWorld()
472 using namespace boost::python;
474 class_<World> ("World", no_init)
475 .def("area", &World::getArea,
476 return_value_policy<reference_existing_object>())
477 .def("focus",
478 static_cast<void (World::*) (Area*,int,int,double)>
479 (&World::focusArea))
480 .def_readwrite("on_key_down", &World::keydownScript)
481 .def_readwrite("on_key_up", &World::keyupScript)