try to make build portable: remove SDL_mixer dependency, remove -f from cp command...
[openc2e.git] / Engine.cpp
blob37af4c87e7a5e353db0f1a88914ba4d1f6d5ebaa
1 /*
2 * Engine.cpp
3 * openc2e
5 * Created by Alyssa Milburn on Tue Nov 28 2006.
6 * Copyright (c) 2006-2008 Alyssa Milburn. All rights reserved.
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
20 #include "Room.h"
21 #include "Engine.h"
22 #include "World.h"
23 #include "MetaRoom.h"
24 #include "caosVM.h" // for setupCommandPointers()
25 #include "caosScript.h" // for executeNetwork()
26 #include "PointerAgent.h"
27 #include "dialect.h" // registerDelegates
28 #include "NullBackend.h"
29 #include "NullAudioBackend.h"
31 #include <boost/filesystem/path.hpp>
32 #include <boost/filesystem/operations.hpp>
33 #include <boost/filesystem/convenience.hpp>
34 #include <boost/program_options.hpp>
35 #include <boost/format.hpp>
36 namespace fs = boost::filesystem;
37 namespace po = boost::program_options;
39 #ifndef _WIN32
40 #include <sys/types.h> // passwd*
41 #include <pwd.h> // getpwuid
42 #endif
44 #ifdef _WIN32
45 #include <shlobj.h>
46 #endif
48 Engine engine;
50 Engine::Engine() {
51 done = false;
52 dorendering = true;
53 fastticks = false;
54 refreshdisplay = false;
56 bmprenderer = false;
58 tickdata = 0;
59 for (unsigned int i = 0; i < 10; i++) ticktimes[i] = 0;
60 ticktimeptr = 0;
61 version = 0; // TODO: something something
63 srand(time(NULL)); // good a place as any :)
65 cmdline_enable_sound = true;
66 cmdline_norun = false;
68 palette = 0;
70 addPossibleBackend("null", shared_ptr<Backend>(new NullBackend()));
71 addPossibleAudioBackend("null", shared_ptr<AudioBackend>(new NullAudioBackend()));
74 Engine::~Engine() {
77 void Engine::addPossibleBackend(std::string s, boost::shared_ptr<Backend> b) {
78 assert(!backend);
79 assert(b);
80 preferred_backend = s;
81 possible_backends[s] = b;
84 void Engine::addPossibleAudioBackend(std::string s, boost::shared_ptr<AudioBackend> b) {
85 assert(!audio);
86 assert(b);
87 preferred_audiobackend = s;
88 possible_audiobackends[s] = b;
91 void Engine::setBackend(shared_ptr<Backend> b) {
92 backend = b;
93 lasttimestamp = backend->ticks();
95 // load palette for C1
96 if (world.gametype == "c1") {
97 // TODO: case-sensitivity for the lose
98 fs::path palpath(world.data_directories[0] / "/Palettes/palette.dta");
99 if (fs::exists(palpath) && !fs::is_directory(palpath)) {
100 palette = new unsigned char[768];
102 std::ifstream f(palpath.native_directory_string().c_str(), std::ios::binary);
103 f >> std::noskipws;
104 f.read((char *)palette, 768);
106 for (unsigned int i = 0; i < 768; i++) {
107 palette[i] = palette[i] * 4;
110 backend->setPalette((uint8 *)palette);
111 } else
112 throw creaturesException("Couldn't find C1 palette data!");
116 std::string Engine::executeNetwork(std::string in) {
117 // now parse and execute the CAOS we obtained
118 caosVM vm(0); // needs to be outside 'try' so we can reset outputstream on exception
119 try {
120 std::istringstream s(in);
121 caosScript script(world.gametype, "<network>"); // XXX
122 script.parse(s);
123 script.installScripts();
124 std::ostringstream o;
125 vm.setOutputStream(o);
126 vm.runEntirely(script.installer);
127 vm.outputstream = 0; // otherwise would point to dead stack
128 return o.str();
129 } catch (std::exception &e) {
130 vm.outputstream = 0; // otherwise would point to dead stack
131 return std::string("### EXCEPTION: ") + e.what();
135 bool Engine::needsUpdate() {
136 return (!world.paused) && (fastticks || (backend->ticks() > (tickdata + world.ticktime)));
139 void Engine::update() {
140 tickdata = backend->ticks();
142 // tick the world
143 world.tick();
145 // draw the world
146 if (dorendering || refreshdisplay) {
147 refreshdisplay = false;
149 if (backend->selfRender()) {
150 // TODO: this makes race/pace hilariously inaccurate, since render time isn't included
151 backend->requestRender();
152 } else {
153 world.drawWorld();
157 // play C1 music
158 // TODO: this doesn't seem to actually be every 7 seconds, but actually somewhat random
159 // TODO: this should be linked to 'real' time, so it doesn't go crazy when game speed is modified
160 // TODO: is this the right place for this?
161 if (version == 1 && (world.tickcount % 70) == 0) {
162 int piece = 1 + (rand() % 28);
163 std::string filename = boost::str(boost::format("MU%02d") % piece);
164 boost::shared_ptr<AudioSource> s = world.playAudio(filename, AgentRef(), false, false);
165 if (s) s->setVolume(0.4f);
168 // update our data for things like pace, race, ticktime, etc
169 ticktimes[ticktimeptr] = backend->ticks() - tickdata;
170 ticktimeptr++;
171 if (ticktimeptr == 10) ticktimeptr = 0;
172 float avgtime = 0;
173 for (unsigned int i = 0; i < 10; i++) avgtime += ((float)ticktimes[i] / world.ticktime);
174 world.pace = avgtime / 10;
176 world.race = backend->ticks() - lasttimestamp;
177 lasttimestamp = backend->ticks();
180 bool Engine::tick() {
181 assert(backend);
182 backend->handleEvents();
184 // tick+draw the world, if necessary
185 bool needupdate = needsUpdate();
186 if (needupdate)
187 update();
189 processEvents();
190 if (needupdate)
191 handleKeyboardScrolling();
193 return needupdate;
196 void Engine::handleKeyboardScrolling() {
197 // keyboard-based scrolling
198 static float accelspeed = 8, decelspeed = .5, maxspeed = 64;
199 static float velx = 0;
200 static float vely = 0;
202 bool wasdMode = false;
203 caosVar v = world.variables["engine_wasd"];
204 if (v.hasInt()) {
205 switch (v.getInt()) {
206 case 1: // enable if CTRL is held
207 wasdMode = backend->keyDown(17); // CTRL
208 break;
209 case 2: // enable unconditionally
210 // (this needs agent support to suppress chat bubbles etc)
211 wasdMode = true;
212 break;
213 case 0: // disable
214 wasdMode = false;
215 break;
216 default: // disable
217 std::cout << "Warning: engine_wasd_scrolling is set to unknown value " << v.getInt() << std::endl;
218 world.variables["engine_wasd_scrolling"] = caosVar(0);
219 wasdMode = false;
220 break;
224 // check keys
225 bool leftdown = backend->keyDown(37)
226 || (wasdMode && a_down);
227 bool rightdown = backend->keyDown(39)
228 || (wasdMode && d_down);
229 bool updown = backend->keyDown(38)
230 || (wasdMode && w_down);
231 bool downdown = backend->keyDown(40)
232 || (wasdMode && s_down);
234 if (leftdown)
235 velx -= accelspeed;
236 if (rightdown)
237 velx += accelspeed;
238 if (!leftdown && !rightdown) {
239 velx *= decelspeed;
240 if (fabs(velx) < 0.1) velx = 0;
242 if (updown)
243 vely -= accelspeed;
244 if (downdown)
245 vely += accelspeed;
246 if (!updown && !downdown) {
247 vely *= decelspeed;
248 if (fabs(vely) < 0.1) vely = 0;
251 // enforced maximum speed
252 if (velx >= maxspeed) velx = maxspeed;
253 else if (velx <= -maxspeed) velx = -maxspeed;
254 if (vely >= maxspeed) vely = maxspeed;
255 else if (vely <= -maxspeed) vely = -maxspeed;
257 // do the actual movement
258 if (velx || vely) {
259 int adjustx = world.camera.getX(), adjusty = world.camera.getY();
260 int adjustbyx = (int)velx, adjustbyy = (int) vely;
262 world.camera.moveTo(adjustx + adjustbyx, adjusty + adjustbyy, jump);
266 void Engine::processEvents() {
267 SomeEvent event;
268 while (backend->pollEvent(event)) {
269 switch (event.type) {
270 case eventresizewindow:
271 handleResizedWindow(event);
272 break;
274 case eventmousemove:
275 handleMouseMove(event);
276 break;
278 case eventmousebuttonup:
279 case eventmousebuttondown:
280 handleMouseButton(event);
281 break;
283 case eventkeydown:
284 handleKeyDown(event);
285 break;
287 case eventspecialkeydown:
288 handleSpecialKeyDown(event);
289 break;
291 case eventspecialkeyup:
292 handleSpecialKeyUp(event);
293 break;
295 case eventquit:
296 done = true;
297 break;
299 default:
300 break;
305 void Engine::handleResizedWindow(SomeEvent &event) {
306 // notify agents
307 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
308 if (!*i) continue;
309 (*i)->queueScript(123, 0); // window resized script
313 void Engine::handleMouseMove(SomeEvent &event) {
314 // move the cursor
315 world.hand()->moveTo(event.x + world.camera.getX(), event.y + world.camera.getY());
316 world.hand()->velx.setInt(event.xrel * 4);
317 world.hand()->vely.setInt(event.yrel * 4);
319 // middle mouse button scrolling
320 if (event.button & buttonmiddle)
321 world.camera.moveTo(world.camera.getX() - event.xrel, world.camera.getY() - event.yrel, jump);
323 // notify agents
324 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
325 if (!*i) continue;
326 if ((*i)->imsk_mouse_move) {
327 caosVar x; x.setFloat(world.hand()->x);
328 caosVar y; y.setFloat(world.hand()->y);
329 (*i)->queueScript(75, 0, x, y); // Raw Mouse Move
334 void Engine::handleMouseButton(SomeEvent &event) {
335 // notify agents
336 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
337 if (!*i) continue;
338 if ((event.type == eventmousebuttonup && (*i)->imsk_mouse_up) ||
339 (event.type == eventmousebuttondown && (*i)->imsk_mouse_down)) {
340 // set the button value as necessary
341 caosVar button;
342 switch (event.button) { // Backend guarantees that only one button will be set on a mousebuttondown event.
343 // the values here make fuzzie suspicious that c2e combines these events
344 // nornagon seems to think c2e doesn't
345 case buttonleft: button.setInt(1); break;
346 case buttonright: button.setInt(2); break;
347 case buttonmiddle: button.setInt(4); break;
348 default: break;
351 // if it was a mouse button we're interested in, then fire the relevant raw event
352 if (button.getInt() != 0) {
353 if (event.type == eventmousebuttonup)
354 (*i)->queueScript(77, 0, button); // Raw Mouse Up
355 else
356 (*i)->queueScript(76, 0, button); // Raw Mouse Down
359 if ((event.type == eventmousebuttondown &&
360 (event.button == buttonwheelup || event.button == buttonwheeldown) &&
361 (*i)->imsk_mouse_wheel)) {
362 // fire the mouse wheel event with the relevant delta value
363 caosVar delta;
364 if (event.button == buttonwheeldown)
365 delta.setInt(-120);
366 else
367 delta.setInt(120);
368 (*i)->queueScript(78, 0, delta); // Raw Mouse Wheel
372 if (!world.hand()->handle_events) return;
373 if (event.type != eventmousebuttondown) return;
375 // do our custom handling
376 if (event.button == buttonleft) {
377 CompoundPart *a = world.partAt(world.hand()->x, world.hand()->y);
378 if (a /* && a->canActivate() */) { // TODO
379 // if the agent isn't paused, tell it to handle a click
380 if (!a->getParent()->paused)
381 a->handleClick(world.hand()->x - a->x - a->getParent()->x, world.hand()->y - a->y - a->getParent()->y);
383 // TODO: not sure how to handle the following properly, needs research..
384 int eve;
385 if (engine.version < 3) {
386 eve = 50;
387 } else {
388 eve = 101;
390 world.hand()->firePointerScript(eve, a->getParent()); // Pointer Activate 1
391 } else if (engine.version > 2)
392 world.hand()->queueScript(116, 0); // Pointer Clicked Background
393 } else if (event.button == buttonright) {
394 if (world.paused) return; // TODO: wrong?
396 // picking up and dropping are implictly handled by the scripts (well, messages) 4 and 5
397 // TODO: check if this is correct behaviour, one issue is that this isn't instant, another
398 // is the messages might only be fired in c2e when you use MESG WRIT, in which case we'll
399 // need to manually set world.hand()->carrying to NULL and a here, respectively - fuzzie
400 if (world.hand()->carrying) {
401 // TODO: c1 support - these attributes are invalid for c1
402 if (!world.hand()->carrying->suffercollisions() || (world.hand()->carrying->validInRoomSystem() || version == 1)) {
403 world.hand()->carrying->queueScript(5, world.hand()); // drop
405 int eve; if (engine.version < 3) eve = 54; else eve = 105;
406 world.hand()->firePointerScript(eve, world.hand()->carrying); // Pointer Drop
408 // TODO: is this the correct check?
409 if (world.hand()->carrying->sufferphysics() && world.hand()->carrying->suffercollisions()) {
410 // TODO: do this in the pointer agent?
411 world.hand()->carrying->velx.setFloat(world.hand()->velx.getFloat());
412 world.hand()->carrying->vely.setFloat(world.hand()->vely.getFloat());
414 } else {
415 // TODO: some kind of "fail to drop" animation/sound?
417 } else {
418 Agent *a = world.agentAt(world.hand()->x, world.hand()->y, false, true);
419 if (a) {
420 a->queueScript(4, world.hand()); // pickup
422 int eve; if (engine.version < 3) eve = 53; else eve = 104;
423 world.hand()->firePointerScript(eve, a); // Pointer Pickup
426 } else if (event.button == buttonmiddle) {
427 std::vector<shared_ptr<Room> > rooms = world.map.roomsAt(event.x + world.camera.getX(), event.y + world.camera.getY());
428 if (rooms.size() > 0) std::cout << "Room at cursor is " << rooms[0]->id << std::endl;
429 Agent *a = world.agentAt(event.x + world.camera.getX(), event.y + world.camera.getY(), true);
430 if (a)
431 std::cout << "Agent under mouse is " << a->identify();
432 else
433 std::cout << "No agent under cursor";
434 std::cout << std::endl;
438 void Engine::handleKeyDown(SomeEvent &event) {
439 switch (event.key) {
440 case 'w': w_down = true; break;
441 case 'a': a_down = true; break;
442 case 's': s_down = true; break;
443 case 'd': d_down = true; break;
446 // tell the agent with keyboard focus
447 if (world.focusagent) {
448 TextEntryPart *t = (TextEntryPart *)((CompoundAgent *)world.focusagent.get())->part(world.focuspart);
449 if (t)
450 t->handleKey(event.key);
453 // notify agents
454 caosVar k;
455 k.setInt(event.key);
456 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
457 if (!*i) continue;
458 if ((*i)->imsk_translated_char)
459 (*i)->queueScript(79, 0, k); // translated char script
463 void Engine::handleSpecialKeyUp(SomeEvent &event) {
464 switch (event.key) {
465 case 0x57:
466 w_down = false;
467 break;
468 case 0x41:
469 a_down = false;
470 break;
471 case 0x53:
472 s_down = false;
473 break;
474 case 0x44:
475 d_down = false;
476 break;
480 void Engine::handleSpecialKeyDown(SomeEvent &event) {
481 switch (event.key) {
482 case 0x57:
483 w_down = true;
484 break;
485 case 0x41:
486 a_down = true;
487 break;
488 case 0x53:
489 s_down = true;
490 break;
491 case 0x44:
492 d_down = true;
493 break;
497 // handle debug keys, if they're enabled
498 caosVar v = world.variables["engine_debug_keys"];
499 if (v.hasInt() && v.getInt() == 1) {
500 if (backend->keyDown(16)) { // shift down
501 MetaRoom *n; // for pageup/pagedown
503 switch (event.key) {
504 case 45: // insert
505 world.showrooms = !world.showrooms;
506 break;
508 case 19: // pause
509 // TODO: debug pause game
510 break;
512 case 32: // space
513 // TODO: force tick
514 break;
516 case 33: // pageup
517 // TODO: previous metaroom
518 if ((world.map.getMetaRoomCount() - 1) == world.camera.getMetaRoom()->id)
519 break;
520 n = world.map.getMetaRoom(world.camera.getMetaRoom()->id + 1);
521 if (n)
522 world.camera.goToMetaRoom(n->id);
523 break;
525 case 34: // pagedown
526 // TODO: next metaroom
527 if (world.camera.getMetaRoom()->id == 0)
528 break;
529 n = world.map.getMetaRoom(world.camera.getMetaRoom()->id - 1);
530 if (n)
531 world.camera.goToMetaRoom(n->id);
532 break;
534 default: break; // to shut up warnings
539 // tell the agent with keyboard focus
540 if (world.focusagent) {
541 TextEntryPart *t = (TextEntryPart *)((CompoundAgent *)world.focusagent.get())->part(world.focuspart);
542 if (t)
543 t->handleSpecialKey(event.key);
546 // notify agents
547 caosVar k;
548 k.setInt(event.key);
549 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
550 if (!*i) continue;
551 if ((*i)->imsk_key_down)
552 (*i)->queueScript(73, 0, k); // key down script
556 static const char data_default[] = "./data";
558 static void opt_version() {
559 // We already showed the primary version bit, just throw in some random legalese
560 std::cout <<
561 "This is free software; see the source for copying conditions. There is NO" << std::endl <<
562 "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." << std::endl << std::endl <<
563 "...please don't sue us." << std::endl;
566 bool Engine::parseCommandLine(int argc, char *argv[]) {
567 // variables for command-line flags
568 int optret;
569 std::vector<std::string> data_vec;
571 // generate help for backend options
572 std::string available_backends;
573 for (std::map<std::string, boost::shared_ptr<Backend> >::iterator i = possible_backends.begin(); i != possible_backends.end(); i++) {
574 if (available_backends.empty()) available_backends = i->first;
575 else available_backends += ", " + i->first;
577 available_backends = "Select the backend (options: " + available_backends + "), default is " + preferred_backend;
579 std::string available_audiobackends;
580 for (std::map<std::string, boost::shared_ptr<AudioBackend> >::iterator i = possible_audiobackends.begin(); i != possible_audiobackends.end(); i++) {
581 if (available_audiobackends.empty()) available_audiobackends = i->first;
582 else available_audiobackends += ", " + i->first;
584 available_audiobackends = "Select the audio backend (options: " + available_audiobackends + "), default is " + preferred_audiobackend;
586 // parse the command-line flags
587 po::options_description desc;
588 desc.add_options()
589 ("help,h", "Display help on command-line options")
590 ("version,V", "Display openc2e version")
591 ("silent,s", "Disable all sounds")
592 ("backend,k", po::value<std::string>(&preferred_backend)->composing(), available_backends.c_str())
593 ("audiobackend,o", po::value<std::string>(&preferred_audiobackend)->composing(), available_audiobackends.c_str())
594 ("data-path,d", po::value< std::vector<std::string> >(&data_vec)->composing(),
595 "Sets or adds a path to a data directory")
596 ("bootstrap,b", po::value< std::vector<std::string> >(&cmdline_bootstrap)->composing(),
597 "Sets or adds a path or COS file to bootstrap from")
598 ("gametype,g", po::value< std::string >(&world.gametype), "Set the game type (c1, c2, cv or c3)")
599 ("gamename,m", po::value< std::string >(&gamename), "Set the game name")
600 ("norun,n", "Don't run the game, just execute scripts")
601 ("autokill,a", "Enable autokill")
602 ("autostop", "Enable autostop (or disable it, for CV)")
604 po::variables_map vm;
605 po::store(po::parse_command_line(argc, argv, desc), vm);
606 po::notify(vm);
608 cmdline_enable_sound = !vm.count("silent");
609 cmdline_norun = vm.count("norun");
611 if (vm.count("help")) {
612 std::cout << desc << std::endl;
613 return false;
616 if (vm.count("version")) {
617 opt_version();
618 return false;
621 if (vm.count("autokill")) {
622 world.autokill = true;
625 if (vm.count("autostop")) {
626 world.autostop = true;
629 if (vm.count("data-path") == 0) {
630 std::cout << "Warning: No data path specified, trying default of '" << data_default << "', see --help if you need to specify one." << std::endl;
631 data_vec.push_back(data_default);
634 // add all the data directories to the list
635 for (std::vector<std::string>::iterator i = data_vec.begin(); i != data_vec.end(); i++) {
636 fs::path datadir(*i, fs::native);
637 if (!fs::exists(datadir)) {
638 throw creaturesException("data path '" + *i + "' doesn't exist");
640 world.data_directories.push_back(datadir);
643 // make a vague attempt at blacklisting some characters inside the gamename
644 // (it's used in directory names, registry keys, etc)
645 std::string invalidchars = "\\/:*?\"<>|";
646 for (unsigned int i = 0; i < invalidchars.size(); i++) {
647 if (gamename.find(invalidchars[i]) != gamename.npos)
648 throw creaturesException(std::string("The character ") + invalidchars[i] + " is not valid in a gamename.");
651 return true;
654 bool Engine::initialSetup() {
655 assert(world.data_directories.size() > 0);
657 // autodetect gametype if necessary
658 if (world.gametype.empty()) {
659 std::cout << "Warning: No gametype specified, ";
660 // TODO: is this sane? especially unsure about about.exe
661 if (!world.findFile("Creatures.exe").empty()) {
662 std::cout << "found Creatures.exe, assuming C1 (c1)";
663 world.gametype = "c1";
664 } else if (!world.findFile("Creatures2.exe").empty()) {
665 std::cout << "found Creatures2.exe, assuming C2 (c2)";
666 world.gametype = "c2";
667 } else if (!world.findFile("Sea-Monkeys.ico").empty()) {
668 std::cout << "found Sea-Monkeys.ico, assuming Sea-Monkeys (sm)";
669 world.gametype = "sm";
670 } else if (!world.findFile("about.exe").empty()) {
671 std::cout << "found about.exe, assuming CA, CP or CV (cv)";
672 world.gametype = "cv";
673 } else {
674 std::cout << "assuming C3/DS (c3)";
675 world.gametype = "c3";
677 std::cout << ", see --help if you need to specify one." << std::endl;
680 // set engine version
681 // TODO: set gamename
682 if (world.gametype == "c1") {
683 if (gamename.empty()) gamename = "Creatures 1";
684 version = 1;
685 } else if (world.gametype == "c2") {
686 if (gamename.empty()) gamename = "Creatures 2";
687 version = 2;
688 } else if (world.gametype == "c3") {
689 if (gamename.empty()) gamename = "Creatures 3";
690 version = 3;
691 } else if (world.gametype == "cv") {
692 if (gamename.empty()) gamename = "Creatures Village";
693 version = 3;
694 world.autostop = !world.autostop;
695 } else if (world.gametype == "sm") {
696 if (gamename.empty()) gamename = "Sea-Monkeys";
697 version = 3;
698 bmprenderer = true;
699 } else
700 throw creaturesException(boost::str(boost::format("unknown gametype '%s'!") % world.gametype));
702 // finally, add our cache directory to the end
703 world.data_directories.push_back(storageDirectory());
705 // initial setup
706 registerDelegates();
707 std::cout << "* Reading catalogue files..." << std::endl;
708 world.initCatalogue();
709 std::cout << "* Initial setup..." << std::endl;
710 world.init(); // just reads mouse cursor (we want this after the catalogue reading so we don't play "guess the filename")
711 if (engine.version > 2) {
712 std::cout << "* Reading PRAY files..." << std::endl;
713 world.praymanager.update();
716 if (cmdline_norun) preferred_backend = "null";
717 if (preferred_backend != "null") std::cout << "* Initialising backend " << preferred_backend << "..." << std::endl;
718 shared_ptr<Backend> b = possible_backends[preferred_backend];
719 if (!b) throw creaturesException("No such backend " + preferred_backend);
720 b->init(); setBackend(b);
721 possible_backends.clear();
723 if (cmdline_norun || !cmdline_enable_sound) preferred_audiobackend = "null";
724 if (preferred_audiobackend != "null") std::cout << "* Initialising audio backend " << preferred_audiobackend << "..." << std::endl;
725 shared_ptr<AudioBackend> a = possible_audiobackends[preferred_audiobackend];
726 if (!a) throw creaturesException("No such audio backend " + preferred_audiobackend);
727 try{
728 a->init(); audio = a;
729 } catch (creaturesException &e) {
730 std::cerr << "* Couldn't initialize backend " << preferred_audiobackend << ": " << e.what() << std::endl << "* Continuing without sound." << std::endl;
731 audio = shared_ptr<AudioBackend>(new NullAudioBackend());
732 audio->init();
734 possible_audiobackends.clear();
736 world.camera.setBackend(backend); // TODO: hrr
738 int listenport = backend->networkInit();
739 if (listenport != -1) {
740 // inform the user of the port used, and store it in the relevant file
741 std::cout << "Listening for connections on port " << listenport << "." << std::endl;
742 #ifndef _WIN32
743 fs::path p = fs::path(homeDirectory().native_directory_string() + "/.creaturesengine", fs::native);
744 if (!fs::exists(p))
745 fs::create_directory(p);
746 if (fs::is_directory(p)) {
747 std::ofstream f((p.native_directory_string() + "/port").c_str(), std::ios::trunc);
748 f << boost::str(boost::format("%d") % listenport);
750 #endif
753 if (world.data_directories.size() < 3) {
754 // TODO: This is a hack for DS, basically. Not sure if it works properly. - fuzzie
755 caosVar name; name.setString("engine_no_auxiliary_bootstrap_1");
756 caosVar contents; contents.setInt(1);
757 eame_variables[name] = contents;
760 // execute the initial scripts!
761 std::cout << "* Executing initial scripts..." << std::endl;
762 if (cmdline_bootstrap.size() == 0) {
763 world.executeBootstrap(false);
764 } else {
765 std::vector<std::string> scripts;
767 for (std::vector< std::string >::iterator bsi = cmdline_bootstrap.begin(); bsi != cmdline_bootstrap.end(); bsi++) {
768 fs::path scriptdir(*bsi, fs::native);
769 if (!fs::exists(scriptdir)) {
770 std::cerr << "Warning: Couldn't find a specified script directory (trying " << *bsi << ")!\n";
771 continue;
773 world.executeBootstrap(scriptdir);
777 // if there aren't any metarooms, we can't run a useful game, the user probably
778 // wanted to execute a CAOS script or something went badly wrong.
779 if (!cmdline_norun && world.map.getMetaRoomCount() == 0) {
780 shutdown();
781 throw creaturesException("No metarooms found in given bootstrap directories or files");
784 std::cout << "* Done startup." << std::endl;
786 if (cmdline_norun) {
787 // TODO: see comment above about avoiding backend when norun is set
788 std::cout << "Told not to run the world, so stopping now." << std::endl;
789 shutdown();
790 return false;
793 return true;
796 void Engine::shutdown() {
797 world.shutdown();
798 backend->shutdown();
799 audio->shutdown();
800 freeDelegates(); // does nothing if there are none (ie, no call to initialSetup)
803 fs::path Engine::homeDirectory() {
804 fs::path p;
806 #ifndef _WIN32
807 char *envhome = getenv("HOME");
808 if (envhome)
809 p = fs::path(envhome, fs::native);
810 if ((!envhome) || (!fs::is_directory(p)))
811 p = fs::path(getpwuid(getuid())->pw_dir, fs::native);
812 if (!fs::is_directory(p)) {
813 std::cerr << "Can't work out what your home directory is, giving up and using /tmp for now." << std::endl;
814 p = fs::path("/tmp", fs::native); // sigh
816 #else
817 TCHAR szPath[_MAX_PATH];
818 SHGetSpecialFolderPath(NULL, szPath, CSIDL_PERSONAL, TRUE);
820 p = fs::path(szPath, fs::native);
821 if (!fs::exists(p) || !fs::is_directory(p))
822 throw creaturesException("Windows reported that your My Documents folder is at '" + std::string(szPath) + "' but there's no directory there!");
823 #endif
825 return p;
828 fs::path Engine::storageDirectory() {
829 #ifndef _WIN32
830 std::string dirname = "/.openc2e";
831 #else
832 std::string dirname = "/My Games";
833 #endif
835 // main storage dir
836 fs::path p = fs::path(homeDirectory().native_directory_string() + dirname, fs::native);
837 if (!fs::exists(p))
838 fs::create_directory(p);
839 else if (!fs::is_directory(p))
840 throw creaturesException("Your openc2e data directory " + p.native_directory_string() + " is a file, not a directory. That's bad.");
842 // game-specific storage dir
843 p = fs::path(p.native_directory_string() + std::string("/" + gamename), fs::native);
844 if (!fs::exists(p))
845 fs::create_directory(p);
846 else if (!fs::is_directory(p))
847 throw creaturesException("Your openc2e game data directory " + p.native_directory_string() + " is a file, not a directory. That's bad.");
849 return p;
852 /* vim: set noet: */