fixes to Vehicle::carry - only reject too-big passengers in c2e mode, plus other...
[openc2e.git] / Engine.cpp
blob42c841bbc6b326289990556f385a8a5b5089c6ad
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 #include <windows.h>
47 #endif
49 Engine engine;
51 Engine::Engine() {
52 done = false;
53 dorendering = true;
54 fastticks = false;
55 refreshdisplay = false;
57 bmprenderer = false;
59 tickdata = 0;
60 for (unsigned int i = 0; i < 10; i++) ticktimes[i] = 0;
61 ticktimeptr = 0;
62 version = 0; // TODO: something something
64 srand(time(NULL)); // good a place as any :)
66 cmdline_enable_sound = true;
67 cmdline_norun = false;
69 palette = 0;
71 addPossibleBackend("null", shared_ptr<Backend>(new NullBackend()));
72 addPossibleAudioBackend("null", shared_ptr<AudioBackend>(new NullAudioBackend()));
75 Engine::~Engine() {
78 void Engine::addPossibleBackend(std::string s, boost::shared_ptr<Backend> b) {
79 assert(!backend);
80 assert(b);
81 preferred_backend = s;
82 possible_backends[s] = b;
85 void Engine::addPossibleAudioBackend(std::string s, boost::shared_ptr<AudioBackend> b) {
86 assert(!audio);
87 assert(b);
88 preferred_audiobackend = s;
89 possible_audiobackends[s] = b;
92 void Engine::setBackend(shared_ptr<Backend> b) {
93 backend = b;
94 lasttimestamp = backend->ticks();
96 // load palette for C1
97 if (world.gametype == "c1") {
98 // TODO: case-sensitivity for the lose
99 fs::path palpath(world.data_directories[0] / "/Palettes/palette.dta");
100 if (fs::exists(palpath) && !fs::is_directory(palpath)) {
101 palette = new unsigned char[768];
103 std::ifstream f(palpath.native_directory_string().c_str(), std::ios::binary);
104 f >> std::noskipws;
105 f.read((char *)palette, 768);
107 for (unsigned int i = 0; i < 768; i++) {
108 palette[i] = palette[i] * 4;
111 backend->setPalette((uint8 *)palette);
112 } else
113 throw creaturesException("Couldn't find C1 palette data!");
117 std::string Engine::executeNetwork(std::string in) {
118 // now parse and execute the CAOS we obtained
119 caosVM vm(0); // needs to be outside 'try' so we can reset outputstream on exception
120 try {
121 std::istringstream s(in);
122 caosScript script(world.gametype, "<network>"); // XXX
123 script.parse(s);
124 script.installScripts();
125 std::ostringstream o;
126 vm.setOutputStream(o);
127 vm.runEntirely(script.installer);
128 vm.outputstream = 0; // otherwise would point to dead stack
129 return o.str();
130 } catch (std::exception &e) {
131 vm.outputstream = 0; // otherwise would point to dead stack
132 return std::string("### EXCEPTION: ") + e.what();
136 bool Engine::needsUpdate() {
137 return (!world.paused) && (fastticks || (backend->ticks() > (tickdata + world.ticktime)));
140 void Engine::update() {
141 tickdata = backend->ticks();
143 // tick the world
144 world.tick();
146 // draw the world
147 if (dorendering || refreshdisplay) {
148 refreshdisplay = false;
150 if (backend->selfRender()) {
151 // TODO: this makes race/pace hilariously inaccurate, since render time isn't included
152 backend->requestRender();
153 } else {
154 world.drawWorld();
158 // play C1 music
159 // TODO: this doesn't seem to actually be every 7 seconds, but actually somewhat random
160 // TODO: this should be linked to 'real' time, so it doesn't go crazy when game speed is modified
161 // TODO: is this the right place for this?
162 if (version == 1 && (world.tickcount % 70) == 0) {
163 int piece = 1 + (rand() % 28);
164 std::string filename = boost::str(boost::format("MU%02d") % piece);
165 boost::shared_ptr<AudioSource> s = world.playAudio(filename, AgentRef(), false, false, true);
166 if (s) s->setVolume(0.4f);
169 // update our data for things like pace, race, ticktime, etc
170 ticktimes[ticktimeptr] = backend->ticks() - tickdata;
171 ticktimeptr++;
172 if (ticktimeptr == 10) ticktimeptr = 0;
173 float avgtime = 0;
174 for (unsigned int i = 0; i < 10; i++) avgtime += ((float)ticktimes[i] / world.ticktime);
175 world.pace = avgtime / 10;
177 world.race = backend->ticks() - lasttimestamp;
178 lasttimestamp = backend->ticks();
181 bool Engine::tick() {
182 assert(backend);
183 backend->handleEvents();
185 // tick+draw the world, if necessary
186 bool needupdate = needsUpdate();
187 if (needupdate)
188 update();
190 processEvents();
191 if (needupdate)
192 handleKeyboardScrolling();
194 return needupdate;
197 void Engine::handleKeyboardScrolling() {
198 // keyboard-based scrolling
199 static float accelspeed = 8, decelspeed = .5, maxspeed = 64;
200 static float velx = 0;
201 static float vely = 0;
203 bool wasdMode = false;
204 caosVar v = world.variables["engine_wasd"];
205 if (v.hasInt()) {
206 switch (v.getInt()) {
207 case 1: // enable if CTRL is held
208 wasdMode = backend->keyDown(17); // CTRL
209 break;
210 case 2: // enable unconditionally
211 // (this needs agent support to suppress chat bubbles etc)
212 wasdMode = true;
213 break;
214 case 0: // disable
215 wasdMode = false;
216 break;
217 default: // disable
218 std::cout << "Warning: engine_wasd_scrolling is set to unknown value " << v.getInt() << std::endl;
219 world.variables["engine_wasd_scrolling"] = caosVar(0);
220 wasdMode = false;
221 break;
225 // check keys
226 bool leftdown = backend->keyDown(37)
227 || (wasdMode && a_down);
228 bool rightdown = backend->keyDown(39)
229 || (wasdMode && d_down);
230 bool updown = backend->keyDown(38)
231 || (wasdMode && w_down);
232 bool downdown = backend->keyDown(40)
233 || (wasdMode && s_down);
235 if (leftdown)
236 velx -= accelspeed;
237 if (rightdown)
238 velx += accelspeed;
239 if (!leftdown && !rightdown) {
240 velx *= decelspeed;
241 if (fabs(velx) < 0.1) velx = 0;
243 if (updown)
244 vely -= accelspeed;
245 if (downdown)
246 vely += accelspeed;
247 if (!updown && !downdown) {
248 vely *= decelspeed;
249 if (fabs(vely) < 0.1) vely = 0;
252 // enforced maximum speed
253 if (velx >= maxspeed) velx = maxspeed;
254 else if (velx <= -maxspeed) velx = -maxspeed;
255 if (vely >= maxspeed) vely = maxspeed;
256 else if (vely <= -maxspeed) vely = -maxspeed;
258 // do the actual movement
259 if (velx || vely) {
260 int adjustx = world.camera.getX(), adjusty = world.camera.getY();
261 int adjustbyx = (int)velx, adjustbyy = (int) vely;
263 world.camera.moveTo(adjustx + adjustbyx, adjusty + adjustbyy, jump);
267 void Engine::processEvents() {
268 SomeEvent event;
269 while (backend->pollEvent(event)) {
270 switch (event.type) {
271 case eventresizewindow:
272 handleResizedWindow(event);
273 break;
275 case eventmousemove:
276 handleMouseMove(event);
277 break;
279 case eventmousebuttonup:
280 case eventmousebuttondown:
281 handleMouseButton(event);
282 break;
284 case eventkeydown:
285 handleKeyDown(event);
286 break;
288 case eventspecialkeydown:
289 handleSpecialKeyDown(event);
290 break;
292 case eventspecialkeyup:
293 handleSpecialKeyUp(event);
294 break;
296 case eventquit:
297 done = true;
298 break;
300 default:
301 break;
306 void Engine::handleResizedWindow(SomeEvent &event) {
307 // notify agents
308 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
309 if (!*i) continue;
310 (*i)->queueScript(123, 0); // window resized script
314 void Engine::handleMouseMove(SomeEvent &event) {
315 // move the cursor
316 world.hand()->handleEvent(event);
318 // notify agents
319 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
320 if (!*i) continue;
321 if ((*i)->imsk_mouse_move) {
322 caosVar x; x.setFloat(world.hand()->x);
323 caosVar y; y.setFloat(world.hand()->y);
324 (*i)->queueScript(75, 0, x, y); // Raw Mouse Move
329 void Engine::handleMouseButton(SomeEvent &event) {
330 // notify agents
331 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
332 if (!*i) continue;
333 if ((event.type == eventmousebuttonup && (*i)->imsk_mouse_up) ||
334 (event.type == eventmousebuttondown && (*i)->imsk_mouse_down)) {
335 // set the button value as necessary
336 caosVar button;
337 switch (event.button) { // Backend guarantees that only one button will be set on a mousebuttondown event.
338 // the values here make fuzzie suspicious that c2e combines these events
339 // nornagon seems to think c2e doesn't
340 case buttonleft: button.setInt(1); break;
341 case buttonright: button.setInt(2); break;
342 case buttonmiddle: button.setInt(4); break;
343 default: break;
346 // if it was a mouse button we're interested in, then fire the relevant raw event
347 if (button.getInt() != 0) {
348 if (event.type == eventmousebuttonup)
349 (*i)->queueScript(77, 0, button); // Raw Mouse Up
350 else
351 (*i)->queueScript(76, 0, button); // Raw Mouse Down
354 if ((event.type == eventmousebuttondown &&
355 (event.button == buttonwheelup || event.button == buttonwheeldown) &&
356 (*i)->imsk_mouse_wheel)) {
357 // fire the mouse wheel event with the relevant delta value
358 caosVar delta;
359 if (event.button == buttonwheeldown)
360 delta.setInt(-120);
361 else
362 delta.setInt(120);
363 (*i)->queueScript(78, 0, delta); // Raw Mouse Wheel
367 world.hand()->handleEvent(event);
370 void Engine::handleKeyDown(SomeEvent &event) {
371 switch (event.key) {
372 case 'w': w_down = true; break;
373 case 'a': a_down = true; break;
374 case 's': s_down = true; break;
375 case 'd': d_down = true; break;
378 // tell the agent with keyboard focus
379 if (world.focusagent) {
380 TextEntryPart *t = (TextEntryPart *)((CompoundAgent *)world.focusagent.get())->part(world.focuspart);
381 if (t)
382 t->handleKey(event.key);
385 // notify agents
386 caosVar k;
387 k.setInt(event.key);
388 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
389 if (!*i) continue;
390 if ((*i)->imsk_translated_char)
391 (*i)->queueScript(79, 0, k); // translated char script
395 void Engine::handleSpecialKeyUp(SomeEvent &event) {
396 switch (event.key) {
397 case 0x57:
398 w_down = false;
399 break;
400 case 0x41:
401 a_down = false;
402 break;
403 case 0x53:
404 s_down = false;
405 break;
406 case 0x44:
407 d_down = false;
408 break;
412 void Engine::handleSpecialKeyDown(SomeEvent &event) {
413 switch (event.key) {
414 case 0x57:
415 w_down = true;
416 break;
417 case 0x41:
418 a_down = true;
419 break;
420 case 0x53:
421 s_down = true;
422 break;
423 case 0x44:
424 d_down = true;
425 break;
429 // handle debug keys, if they're enabled
430 caosVar v = world.variables["engine_debug_keys"];
431 if (v.hasInt() && v.getInt() == 1) {
432 if (backend->keyDown(16)) { // shift down
433 MetaRoom *n; // for pageup/pagedown
435 switch (event.key) {
436 case 45: // insert
437 world.showrooms = !world.showrooms;
438 break;
440 case 19: // pause
441 // TODO: debug pause game
442 break;
444 case 32: // space
445 // TODO: force tick
446 break;
448 case 33: // pageup
449 // TODO: previous metaroom
450 if ((world.map.getMetaRoomCount() - 1) == world.camera.getMetaRoom()->id)
451 break;
452 n = world.map.getMetaRoom(world.camera.getMetaRoom()->id + 1);
453 if (n)
454 world.camera.goToMetaRoom(n->id);
455 break;
457 case 34: // pagedown
458 // TODO: next metaroom
459 if (world.camera.getMetaRoom()->id == 0)
460 break;
461 n = world.map.getMetaRoom(world.camera.getMetaRoom()->id - 1);
462 if (n)
463 world.camera.goToMetaRoom(n->id);
464 break;
466 default: break; // to shut up warnings
471 // tell the agent with keyboard focus
472 if (world.focusagent) {
473 TextEntryPart *t = (TextEntryPart *)((CompoundAgent *)world.focusagent.get())->part(world.focuspart);
474 if (t)
475 t->handleSpecialKey(event.key);
478 // notify agents
479 caosVar k;
480 k.setInt(event.key);
481 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
482 if (!*i) continue;
483 if ((*i)->imsk_key_down)
484 (*i)->queueScript(73, 0, k); // key down script
488 static const char data_default[] = "./data";
490 static void opt_version() {
491 // We already showed the primary version bit, just throw in some random legalese
492 std::cout <<
493 "This is free software; see the source for copying conditions. There is NO" << std::endl <<
494 "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." << std::endl << std::endl <<
495 "...please don't sue us." << std::endl;
498 bool Engine::parseCommandLine(int argc, char *argv[]) {
499 // variables for command-line flags
500 int optret;
501 std::vector<std::string> data_vec;
503 // generate help for backend options
504 std::string available_backends;
505 for (std::map<std::string, boost::shared_ptr<Backend> >::iterator i = possible_backends.begin(); i != possible_backends.end(); i++) {
506 if (available_backends.empty()) available_backends = i->first;
507 else available_backends += ", " + i->first;
509 available_backends = "Select the backend (options: " + available_backends + "), default is " + preferred_backend;
511 std::string available_audiobackends;
512 for (std::map<std::string, boost::shared_ptr<AudioBackend> >::iterator i = possible_audiobackends.begin(); i != possible_audiobackends.end(); i++) {
513 if (available_audiobackends.empty()) available_audiobackends = i->first;
514 else available_audiobackends += ", " + i->first;
516 available_audiobackends = "Select the audio backend (options: " + available_audiobackends + "), default is " + preferred_audiobackend;
518 // parse the command-line flags
519 po::options_description desc;
520 desc.add_options()
521 ("help,h", "Display help on command-line options")
522 ("version,V", "Display openc2e version")
523 ("silent,s", "Disable all sounds")
524 ("backend,k", po::value<std::string>(&preferred_backend)->composing(), available_backends.c_str())
525 ("audiobackend,o", po::value<std::string>(&preferred_audiobackend)->composing(), available_audiobackends.c_str())
526 ("data-path,d", po::value< std::vector<std::string> >(&data_vec)->composing(),
527 "Sets or adds a path to a data directory")
528 ("bootstrap,b", po::value< std::vector<std::string> >(&cmdline_bootstrap)->composing(),
529 "Sets or adds a path or COS file to bootstrap from")
530 ("gametype,g", po::value< std::string >(&world.gametype), "Set the game type (c1, c2, cv or c3)")
531 ("gamename,m", po::value< std::string >(&gamename), "Set the game name")
532 ("norun,n", "Don't run the game, just execute scripts")
533 ("autokill,a", "Enable autokill")
534 ("autostop", "Enable autostop (or disable it, for CV)")
536 po::variables_map vm;
537 po::store(po::parse_command_line(argc, argv, desc), vm);
538 po::notify(vm);
540 cmdline_enable_sound = !vm.count("silent");
541 cmdline_norun = vm.count("norun");
543 if (vm.count("help")) {
544 std::cout << desc << std::endl;
545 return false;
548 if (vm.count("version")) {
549 opt_version();
550 return false;
553 if (vm.count("autokill")) {
554 world.autokill = true;
557 if (vm.count("autostop")) {
558 world.autostop = true;
561 if (vm.count("data-path") == 0) {
562 std::cout << "Warning: No data path specified, trying default of '" << data_default << "', see --help if you need to specify one." << std::endl;
563 data_vec.push_back(data_default);
566 // add all the data directories to the list
567 for (std::vector<std::string>::iterator i = data_vec.begin(); i != data_vec.end(); i++) {
568 fs::path datadir(*i, fs::native);
569 if (!fs::exists(datadir)) {
570 throw creaturesException("data path '" + *i + "' doesn't exist");
572 world.data_directories.push_back(datadir);
575 // make a vague attempt at blacklisting some characters inside the gamename
576 // (it's used in directory names, registry keys, etc)
577 std::string invalidchars = "\\/:*?\"<>|";
578 for (unsigned int i = 0; i < invalidchars.size(); i++) {
579 if (gamename.find(invalidchars[i]) != gamename.npos)
580 throw creaturesException(std::string("The character ") + invalidchars[i] + " is not valid in a gamename.");
583 return true;
586 bool Engine::initialSetup() {
587 assert(world.data_directories.size() > 0);
589 // autodetect gametype if necessary
590 if (world.gametype.empty()) {
591 std::cout << "Warning: No gametype specified, ";
592 // TODO: is this sane? especially unsure about about.exe
593 if (!world.findFile("Creatures.exe").empty()) {
594 std::cout << "found Creatures.exe, assuming C1 (c1)";
595 world.gametype = "c1";
596 } else if (!world.findFile("Creatures2.exe").empty()) {
597 std::cout << "found Creatures2.exe, assuming C2 (c2)";
598 world.gametype = "c2";
599 } else if (!world.findFile("Sea-Monkeys.ico").empty()) {
600 std::cout << "found Sea-Monkeys.ico, assuming Sea-Monkeys (sm)";
601 world.gametype = "sm";
602 } else if (!world.findFile("about.exe").empty()) {
603 std::cout << "found about.exe, assuming CA, CP or CV (cv)";
604 world.gametype = "cv";
605 } else {
606 std::cout << "assuming C3/DS (c3)";
607 world.gametype = "c3";
609 std::cout << ", see --help if you need to specify one." << std::endl;
612 // set engine version
613 // TODO: set gamename
614 if (world.gametype == "c1") {
615 if (gamename.empty()) gamename = "Creatures 1";
616 version = 1;
617 } else if (world.gametype == "c2") {
618 if (gamename.empty()) gamename = "Creatures 2";
619 version = 2;
620 } else if (world.gametype == "c3") {
621 if (gamename.empty()) gamename = "Creatures 3";
622 version = 3;
623 } else if (world.gametype == "cv") {
624 if (gamename.empty()) gamename = "Creatures Village";
625 version = 3;
626 world.autostop = !world.autostop;
627 } else if (world.gametype == "sm") {
628 if (gamename.empty()) gamename = "Sea-Monkeys";
629 version = 3;
630 bmprenderer = true;
631 } else
632 throw creaturesException(boost::str(boost::format("unknown gametype '%s'!") % world.gametype));
634 // finally, add our cache directory to the end
635 world.data_directories.push_back(storageDirectory());
637 // initial setup
638 registerDelegates();
639 std::cout << "* Reading catalogue files..." << std::endl;
640 world.initCatalogue();
641 std::cout << "* Initial setup..." << std::endl;
642 world.init(); // just reads mouse cursor (we want this after the catalogue reading so we don't play "guess the filename")
643 if (engine.version > 2) {
644 std::cout << "* Reading PRAY files..." << std::endl;
645 world.praymanager.update();
648 #ifdef _WIN32
649 // Here we need to set the working directory since apparently windows != clever
650 char exepath[MAX_PATH] = "";
651 GetModuleFileName(0, exepath, sizeof(exepath) - 1);
652 char *exedir = strrchr(exepath, '\\');
653 if(exedir) {
654 // null terminate the string
655 *exedir = 0;
656 // Set working directory
657 SetCurrentDirectory(exepath);
659 else // err, oops
660 std::cerr << "Warning: Setting working directory to " << exepath << " failed.";
661 #endif
663 if (cmdline_norun) preferred_backend = "null";
664 if (preferred_backend != "null") std::cout << "* Initialising backend " << preferred_backend << "..." << std::endl;
665 shared_ptr<Backend> b = possible_backends[preferred_backend];
666 if (!b) throw creaturesException("No such backend " + preferred_backend);
667 b->init(); setBackend(b);
668 possible_backends.clear();
670 if (cmdline_norun || !cmdline_enable_sound) preferred_audiobackend = "null";
671 if (preferred_audiobackend != "null") std::cout << "* Initialising audio backend " << preferred_audiobackend << "..." << std::endl;
672 shared_ptr<AudioBackend> a = possible_audiobackends[preferred_audiobackend];
673 if (!a) throw creaturesException("No such audio backend " + preferred_audiobackend);
674 try{
675 a->init(); audio = a;
676 } catch (creaturesException &e) {
677 std::cerr << "* Couldn't initialize backend " << preferred_audiobackend << ": " << e.what() << std::endl << "* Continuing without sound." << std::endl;
678 audio = shared_ptr<AudioBackend>(new NullAudioBackend());
679 audio->init();
681 possible_audiobackends.clear();
683 world.camera.setBackend(backend); // TODO: hrr
685 int listenport = backend->networkInit();
686 if (listenport != -1) {
687 // inform the user of the port used, and store it in the relevant file
688 std::cout << "Listening for connections on port " << listenport << "." << std::endl;
689 #ifndef _WIN32
690 fs::path p = fs::path(homeDirectory().native_directory_string() + "/.creaturesengine", fs::native);
691 if (!fs::exists(p))
692 fs::create_directory(p);
693 if (fs::is_directory(p)) {
694 std::ofstream f((p.native_directory_string() + "/port").c_str(), std::ios::trunc);
695 f << boost::str(boost::format("%d") % listenport);
697 #endif
700 if (world.data_directories.size() < 3) {
701 // TODO: This is a hack for DS, basically. Not sure if it works properly. - fuzzie
702 caosVar name; name.setString("engine_no_auxiliary_bootstrap_1");
703 caosVar contents; contents.setInt(1);
704 eame_variables[name] = contents;
707 // execute the initial scripts!
708 std::cout << "* Executing initial scripts..." << std::endl;
709 if (cmdline_bootstrap.size() == 0) {
710 world.executeBootstrap(false);
711 } else {
712 std::vector<std::string> scripts;
714 for (std::vector< std::string >::iterator bsi = cmdline_bootstrap.begin(); bsi != cmdline_bootstrap.end(); bsi++) {
715 fs::path scriptdir(*bsi, fs::native);
716 if (!fs::exists(scriptdir)) {
717 std::cerr << "Warning: Couldn't find a specified script directory (trying " << *bsi << ")!\n";
718 continue;
720 world.executeBootstrap(scriptdir);
724 // if there aren't any metarooms, we can't run a useful game, the user probably
725 // wanted to execute a CAOS script or something went badly wrong.
726 if (!cmdline_norun && world.map.getMetaRoomCount() == 0) {
727 shutdown();
728 throw creaturesException("No metarooms found in given bootstrap directories or files");
731 std::cout << "* Done startup." << std::endl;
733 if (cmdline_norun) {
734 // TODO: see comment above about avoiding backend when norun is set
735 std::cout << "Told not to run the world, so stopping now." << std::endl;
736 shutdown();
737 return false;
740 return true;
743 void Engine::shutdown() {
744 world.shutdown();
745 backend->shutdown();
746 audio->shutdown();
747 freeDelegates(); // does nothing if there are none (ie, no call to initialSetup)
750 fs::path Engine::homeDirectory() {
751 fs::path p;
753 #ifndef _WIN32
754 char *envhome = getenv("HOME");
755 if (envhome)
756 p = fs::path(envhome, fs::native);
757 if ((!envhome) || (!fs::is_directory(p)))
758 p = fs::path(getpwuid(getuid())->pw_dir, fs::native);
759 if (!fs::is_directory(p)) {
760 std::cerr << "Can't work out what your home directory is, giving up and using /tmp for now." << std::endl;
761 p = fs::path("/tmp", fs::native); // sigh
763 #else
764 TCHAR szPath[_MAX_PATH];
765 SHGetSpecialFolderPath(NULL, szPath, CSIDL_PERSONAL, TRUE);
767 p = fs::path(szPath, fs::native);
768 if (!fs::exists(p) || !fs::is_directory(p))
769 throw creaturesException("Windows reported that your My Documents folder is at '" + std::string(szPath) + "' but there's no directory there!");
770 #endif
772 return p;
775 fs::path Engine::storageDirectory() {
776 #ifndef _WIN32
777 std::string dirname = "/.openc2e";
778 #else
779 std::string dirname = "/My Games";
780 #endif
782 // main storage dir
783 fs::path p = fs::path(homeDirectory().native_directory_string() + dirname, fs::native);
784 if (!fs::exists(p))
785 fs::create_directory(p);
786 else if (!fs::is_directory(p))
787 throw creaturesException("Your openc2e data directory " + p.native_directory_string() + " is a file, not a directory. That's bad.");
789 // game-specific storage dir
790 p = fs::path(p.native_directory_string() + std::string("/" + gamename), fs::native);
791 if (!fs::exists(p))
792 fs::create_directory(p);
793 else if (!fs::is_directory(p))
794 throw creaturesException("Your openc2e game data directory " + p.native_directory_string() + " is a file, not a directory. That's bad.");
796 return p;
799 /* vim: set noet: */