add hacky implementation of C1 speech/thought bubbles, unfinished
[openc2e.git] / Engine.cpp
blob4ae24c5a048c5aaebc188b1fed0dda64084c9df0
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"
30 #include "SFCFile.h"
32 #include <boost/filesystem/path.hpp>
33 #include <boost/filesystem/operations.hpp>
34 #include <boost/filesystem/convenience.hpp>
35 #include <boost/program_options.hpp>
36 #include <boost/format.hpp>
37 namespace fs = boost::filesystem;
38 namespace po = boost::program_options;
40 #ifndef _WIN32
41 #include <sys/types.h> // passwd*
42 #include <pwd.h> // getpwuid
43 #endif
45 #ifdef _WIN32
46 #include <shlobj.h>
47 #include <windows.h>
48 #endif
50 Engine engine;
52 Engine::Engine() {
53 done = false;
54 dorendering = true;
55 fastticks = false;
56 refreshdisplay = false;
58 bmprenderer = false;
60 tickdata = 0;
61 for (unsigned int i = 0; i < 10; i++) ticktimes[i] = 0;
62 ticktimeptr = 0;
63 version = 0; // TODO: something something
65 srand(time(NULL)); // good a place as any :)
67 cmdline_enable_sound = true;
68 cmdline_norun = false;
70 palette = 0;
72 addPossibleBackend("null", shared_ptr<Backend>(new NullBackend()));
73 addPossibleAudioBackend("null", shared_ptr<AudioBackend>(new NullAudioBackend()));
76 Engine::~Engine() {
79 void Engine::addPossibleBackend(std::string s, boost::shared_ptr<Backend> b) {
80 assert(!backend);
81 assert(b);
82 preferred_backend = s;
83 possible_backends[s] = b;
86 void Engine::addPossibleAudioBackend(std::string s, boost::shared_ptr<AudioBackend> b) {
87 assert(!audio);
88 assert(b);
89 preferred_audiobackend = s;
90 possible_audiobackends[s] = b;
93 void Engine::setBackend(shared_ptr<Backend> b) {
94 backend = b;
95 lasttimestamp = backend->ticks();
97 // load palette for C1
98 if (world.gametype == "c1") {
99 // TODO: case-sensitivity for the lose
100 fs::path palpath(world.data_directories[0] / "/Palettes/palette.dta");
101 if (fs::exists(palpath) && !fs::is_directory(palpath)) {
102 palette = new unsigned char[768];
104 std::ifstream f(palpath.native_directory_string().c_str(), std::ios::binary);
105 f >> std::noskipws;
106 f.read((char *)palette, 768);
108 for (unsigned int i = 0; i < 768; i++) {
109 palette[i] = palette[i] * 4;
112 backend->setPalette((uint8 *)palette);
113 } else
114 throw creaturesException("Couldn't find C1 palette data!");
118 std::string Engine::executeNetwork(std::string in) {
119 // now parse and execute the CAOS we obtained
120 caosVM vm(0); // needs to be outside 'try' so we can reset outputstream on exception
121 try {
122 std::istringstream s(in);
123 caosScript script(world.gametype, "<network>"); // XXX
124 script.parse(s);
125 script.installScripts();
126 std::ostringstream o;
127 vm.setOutputStream(o);
128 vm.runEntirely(script.installer);
129 vm.outputstream = 0; // otherwise would point to dead stack
130 return o.str();
131 } catch (std::exception &e) {
132 vm.outputstream = 0; // otherwise would point to dead stack
133 return std::string("### EXCEPTION: ") + e.what();
137 bool Engine::needsUpdate() {
138 return (!world.paused) && (fastticks || (backend->ticks() > (tickdata + world.ticktime)));
141 unsigned int Engine::msUntilTick() {
142 if (fastticks) return 0;
143 if (world.paused) return world.ticktime; // TODO: correct?
145 int ival = (tickdata + world.ticktime) - backend->ticks();
146 return (ival < 0) ? 0 : ival;
149 void Engine::update() {
150 tickdata = backend->ticks();
152 // tick the world
153 world.tick();
155 // draw the world
156 if (dorendering || refreshdisplay) {
157 refreshdisplay = false;
159 if (backend->selfRender()) {
160 // TODO: this makes race/pace hilariously inaccurate, since render time isn't included
161 backend->requestRender();
162 } else {
163 world.drawWorld();
167 // play C1 music
168 // TODO: this doesn't seem to actually be every 7 seconds, but actually somewhat random
169 // TODO: this should be linked to 'real' time, so it doesn't go crazy when game speed is modified
170 // TODO: is this the right place for this?
171 if (version == 1 && (world.tickcount % 70) == 0) {
172 int piece = 1 + (rand() % 28);
173 std::string filename = boost::str(boost::format("MU%02d") % piece);
174 boost::shared_ptr<AudioSource> s = world.playAudio(filename, AgentRef(), false, false, true);
175 if (s) s->setVolume(0.4f);
178 // update our data for things like pace, race, ticktime, etc
179 ticktimes[ticktimeptr] = backend->ticks() - tickdata;
180 ticktimeptr++;
181 if (ticktimeptr == 10) ticktimeptr = 0;
182 float avgtime = 0;
183 for (unsigned int i = 0; i < 10; i++) avgtime += ((float)ticktimes[i] / world.ticktime);
184 world.pace = avgtime / 10;
186 world.race = backend->ticks() - lasttimestamp;
187 lasttimestamp = backend->ticks();
190 bool Engine::tick() {
191 assert(backend);
192 backend->handleEvents();
194 // tick+draw the world, if necessary
195 bool needupdate = needsUpdate();
196 if (needupdate)
197 update();
199 processEvents();
200 if (needupdate)
201 handleKeyboardScrolling();
203 return needupdate;
206 void Engine::handleKeyboardScrolling() {
207 // keyboard-based scrolling
208 static float accelspeed = 8, decelspeed = .5, maxspeed = 64;
209 static float velx = 0;
210 static float vely = 0;
212 bool wasdMode = false;
213 caosVar v = world.variables["engine_wasd"];
214 if (v.hasInt()) {
215 switch (v.getInt()) {
216 case 1: // enable if CTRL is held
217 wasdMode = backend->keyDown(17); // CTRL
218 break;
219 case 2: // enable unconditionally
220 // (this needs agent support to suppress chat bubbles etc)
221 wasdMode = true;
222 break;
223 case 0: // disable
224 wasdMode = false;
225 break;
226 default: // disable
227 std::cout << "Warning: engine_wasd_scrolling is set to unknown value " << v.getInt() << std::endl;
228 world.variables["engine_wasd_scrolling"] = caosVar(0);
229 wasdMode = false;
230 break;
234 // check keys
235 bool leftdown = backend->keyDown(37)
236 || (wasdMode && a_down);
237 bool rightdown = backend->keyDown(39)
238 || (wasdMode && d_down);
239 bool updown = backend->keyDown(38)
240 || (wasdMode && w_down);
241 bool downdown = backend->keyDown(40)
242 || (wasdMode && s_down);
244 if (leftdown)
245 velx -= accelspeed;
246 if (rightdown)
247 velx += accelspeed;
248 if (!leftdown && !rightdown) {
249 velx *= decelspeed;
250 if (fabs(velx) < 0.1) velx = 0;
252 if (updown)
253 vely -= accelspeed;
254 if (downdown)
255 vely += accelspeed;
256 if (!updown && !downdown) {
257 vely *= decelspeed;
258 if (fabs(vely) < 0.1) vely = 0;
261 // enforced maximum speed
262 if (velx >= maxspeed) velx = maxspeed;
263 else if (velx <= -maxspeed) velx = -maxspeed;
264 if (vely >= maxspeed) vely = maxspeed;
265 else if (vely <= -maxspeed) vely = -maxspeed;
267 // do the actual movement
268 if (velx || vely) {
269 int adjustx = world.camera.getX(), adjusty = world.camera.getY();
270 int adjustbyx = (int)velx, adjustbyy = (int) vely;
272 world.camera.moveTo(adjustx + adjustbyx, adjusty + adjustbyy, jump);
276 void Engine::processEvents() {
277 SomeEvent event;
278 while (backend->pollEvent(event)) {
279 switch (event.type) {
280 case eventresizewindow:
281 handleResizedWindow(event);
282 break;
284 case eventmousemove:
285 handleMouseMove(event);
286 break;
288 case eventmousebuttonup:
289 case eventmousebuttondown:
290 handleMouseButton(event);
291 break;
293 case eventkeydown:
294 handleKeyDown(event);
295 break;
297 case eventspecialkeydown:
298 handleSpecialKeyDown(event);
299 break;
301 case eventspecialkeyup:
302 handleSpecialKeyUp(event);
303 break;
305 case eventquit:
306 done = true;
307 break;
309 default:
310 break;
315 void Engine::handleResizedWindow(SomeEvent &event) {
316 // notify agents
317 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
318 if (!*i) continue;
319 (*i)->queueScript(123, 0); // window resized script
323 void Engine::handleMouseMove(SomeEvent &event) {
324 // move the cursor
325 world.hand()->handleEvent(event);
327 // notify agents
328 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
329 if (!*i) continue;
330 if ((*i)->imsk_mouse_move) {
331 caosVar x; x.setFloat(world.hand()->pointerX());
332 caosVar y; y.setFloat(world.hand()->pointerY());
333 (*i)->queueScript(75, 0, x, y); // Raw Mouse Move
338 void Engine::handleMouseButton(SomeEvent &event) {
339 // notify agents
340 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
341 if (!*i) continue;
342 if ((event.type == eventmousebuttonup && (*i)->imsk_mouse_up) ||
343 (event.type == eventmousebuttondown && (*i)->imsk_mouse_down)) {
344 // set the button value as necessary
345 caosVar button;
346 switch (event.button) { // Backend guarantees that only one button will be set on a mousebuttondown event.
347 // the values here make fuzzie suspicious that c2e combines these events
348 // nornagon seems to think c2e doesn't
349 case buttonleft: button.setInt(1); break;
350 case buttonright: button.setInt(2); break;
351 case buttonmiddle: button.setInt(4); break;
352 default: break;
355 // if it was a mouse button we're interested in, then fire the relevant raw event
356 if (button.getInt() != 0) {
357 if (event.type == eventmousebuttonup)
358 (*i)->queueScript(77, 0, button); // Raw Mouse Up
359 else
360 (*i)->queueScript(76, 0, button); // Raw Mouse Down
363 if ((event.type == eventmousebuttondown &&
364 (event.button == buttonwheelup || event.button == buttonwheeldown) &&
365 (*i)->imsk_mouse_wheel)) {
366 // fire the mouse wheel event with the relevant delta value
367 caosVar delta;
368 if (event.button == buttonwheeldown)
369 delta.setInt(-120);
370 else
371 delta.setInt(120);
372 (*i)->queueScript(78, 0, delta); // Raw Mouse Wheel
376 world.hand()->handleEvent(event);
379 void Engine::handleKeyDown(SomeEvent &event) {
380 switch (event.key) {
381 case 'w': w_down = true; break;
382 case 'a': a_down = true; break;
383 case 's': s_down = true; break;
384 case 'd': d_down = true; break;
387 if (version < 3 && !world.focusagent) {
388 world.hand()->makeNewSpeechBubble();
391 // tell the agent with keyboard focus
392 if (world.focusagent) {
393 CompoundPart *t = world.focusagent.get()->part(world.focuspart);
394 if (t && t->canGainFocus())
395 t->handleKey(event.key);
398 // notify agents
399 caosVar k;
400 k.setInt(event.key);
401 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
402 if (!*i) continue;
403 if ((*i)->imsk_translated_char)
404 (*i)->queueScript(79, 0, k); // translated char script
408 void Engine::handleSpecialKeyUp(SomeEvent &event) {
409 switch (event.key) {
410 case 0x57:
411 w_down = false;
412 break;
413 case 0x41:
414 a_down = false;
415 break;
416 case 0x53:
417 s_down = false;
418 break;
419 case 0x44:
420 d_down = false;
421 break;
425 void Engine::handleSpecialKeyDown(SomeEvent &event) {
426 switch (event.key) {
427 case 0x57:
428 w_down = true;
429 break;
430 case 0x41:
431 a_down = true;
432 break;
433 case 0x53:
434 s_down = true;
435 break;
436 case 0x44:
437 d_down = true;
438 break;
442 // handle debug keys, if they're enabled
443 caosVar v = world.variables["engine_debug_keys"];
444 if (v.hasInt() && v.getInt() == 1) {
445 if (backend->keyDown(16)) { // shift down
446 MetaRoom *n; // for pageup/pagedown
448 switch (event.key) {
449 case 45: // insert
450 world.showrooms = !world.showrooms;
451 break;
453 case 19: // pause
454 // TODO: debug pause game
455 break;
457 case 32: // space
458 // TODO: force tick
459 break;
461 case 33: // pageup
462 // TODO: previous metaroom
463 if ((world.map.getMetaRoomCount() - 1) == world.camera.getMetaRoom()->id)
464 break;
465 n = world.map.getMetaRoom(world.camera.getMetaRoom()->id + 1);
466 if (n)
467 world.camera.goToMetaRoom(n->id);
468 break;
470 case 34: // pagedown
471 // TODO: next metaroom
472 if (world.camera.getMetaRoom()->id == 0)
473 break;
474 n = world.map.getMetaRoom(world.camera.getMetaRoom()->id - 1);
475 if (n)
476 world.camera.goToMetaRoom(n->id);
477 break;
479 default: break; // to shut up warnings
484 // tell the agent with keyboard focus
485 if (world.focusagent) {
486 CompoundPart *t = world.focusagent.get()->part(world.focuspart);
487 if (t && t->canGainFocus())
488 t->handleSpecialKey(event.key);
491 // notify agents
492 caosVar k;
493 k.setInt(event.key);
494 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
495 if (!*i) continue;
496 if ((*i)->imsk_key_down)
497 (*i)->queueScript(73, 0, k); // key down script
501 static const char data_default[] = "./data";
503 static void opt_version() {
504 // We already showed the primary version bit, just throw in some random legalese
505 std::cout <<
506 "This is free software; see the source for copying conditions. There is NO" << std::endl <<
507 "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." << std::endl << std::endl <<
508 "...please don't sue us." << std::endl;
511 bool Engine::parseCommandLine(int argc, char *argv[]) {
512 // variables for command-line flags
513 int optret;
514 std::vector<std::string> data_vec;
516 // generate help for backend options
517 std::string available_backends;
518 for (std::map<std::string, boost::shared_ptr<Backend> >::iterator i = possible_backends.begin(); i != possible_backends.end(); i++) {
519 if (available_backends.empty()) available_backends = i->first;
520 else available_backends += ", " + i->first;
522 available_backends = "Select the backend (options: " + available_backends + "), default is " + preferred_backend;
524 std::string available_audiobackends;
525 for (std::map<std::string, boost::shared_ptr<AudioBackend> >::iterator i = possible_audiobackends.begin(); i != possible_audiobackends.end(); i++) {
526 if (available_audiobackends.empty()) available_audiobackends = i->first;
527 else available_audiobackends += ", " + i->first;
529 available_audiobackends = "Select the audio backend (options: " + available_audiobackends + "), default is " + preferred_audiobackend;
531 // parse the command-line flags
532 po::options_description desc;
533 desc.add_options()
534 ("help,h", "Display help on command-line options")
535 ("version,V", "Display openc2e version")
536 ("silent,s", "Disable all sounds")
537 ("backend,k", po::value<std::string>(&preferred_backend)->composing(), available_backends.c_str())
538 ("audiobackend,o", po::value<std::string>(&preferred_audiobackend)->composing(), available_audiobackends.c_str())
539 ("data-path,d", po::value< std::vector<std::string> >(&data_vec)->composing(),
540 "Sets or adds a path to a data directory")
541 ("bootstrap,b", po::value< std::vector<std::string> >(&cmdline_bootstrap)->composing(),
542 "Sets or adds a path or COS file to bootstrap from")
543 ("gametype,g", po::value< std::string >(&world.gametype), "Set the game type (c1, c2, cv or c3)")
544 ("gamename,m", po::value< std::string >(&gamename), "Set the game name")
545 ("norun,n", "Don't run the game, just execute scripts")
546 ("autokill,a", "Enable autokill")
547 ("autostop", "Enable autostop (or disable it, for CV)")
549 po::variables_map vm;
550 po::store(po::parse_command_line(argc, argv, desc), vm);
551 po::notify(vm);
553 cmdline_enable_sound = !vm.count("silent");
554 cmdline_norun = vm.count("norun");
556 if (vm.count("help")) {
557 std::cout << desc << std::endl;
558 return false;
561 if (vm.count("version")) {
562 opt_version();
563 return false;
566 if (vm.count("autokill")) {
567 world.autokill = true;
570 if (vm.count("autostop")) {
571 world.autostop = true;
574 if (vm.count("data-path") == 0) {
575 std::cout << "Warning: No data path specified, trying default of '" << data_default << "', see --help if you need to specify one." << std::endl;
576 data_vec.push_back(data_default);
579 // add all the data directories to the list
580 for (std::vector<std::string>::iterator i = data_vec.begin(); i != data_vec.end(); i++) {
581 fs::path datadir(*i, fs::native);
582 if (!fs::exists(datadir)) {
583 throw creaturesException("data path '" + *i + "' doesn't exist");
585 world.data_directories.push_back(datadir);
588 // make a vague attempt at blacklisting some characters inside the gamename
589 // (it's used in directory names, registry keys, etc)
590 std::string invalidchars = "\\/:*?\"<>|";
591 for (unsigned int i = 0; i < invalidchars.size(); i++) {
592 if (gamename.find(invalidchars[i]) != gamename.npos)
593 throw creaturesException(std::string("The character ") + invalidchars[i] + " is not valid in a gamename.");
596 return true;
599 bool Engine::initialSetup() {
600 assert(world.data_directories.size() > 0);
602 // autodetect gametype if necessary
603 if (world.gametype.empty()) {
604 std::cout << "Warning: No gametype specified, ";
605 // TODO: is this sane? especially unsure about about.exe
606 if (!world.findFile("Creatures.exe").empty()) {
607 std::cout << "found Creatures.exe, assuming C1 (c1)";
608 world.gametype = "c1";
609 } else if (!world.findFile("Creatures2.exe").empty()) {
610 std::cout << "found Creatures2.exe, assuming C2 (c2)";
611 world.gametype = "c2";
612 } else if (!world.findFile("Sea-Monkeys.ico").empty()) {
613 std::cout << "found Sea-Monkeys.ico, assuming Sea-Monkeys (sm)";
614 world.gametype = "sm";
615 } else if (!world.findFile("about.exe").empty()) {
616 std::cout << "found about.exe, assuming CA, CP or CV (cv)";
617 world.gametype = "cv";
618 } else {
619 std::cout << "assuming C3/DS (c3)";
620 world.gametype = "c3";
622 std::cout << ", see --help if you need to specify one." << std::endl;
625 // set engine version
626 // TODO: set gamename
627 if (world.gametype == "c1") {
628 if (gamename.empty()) gamename = "Creatures 1";
629 version = 1;
630 } else if (world.gametype == "c2") {
631 if (gamename.empty()) gamename = "Creatures 2";
632 version = 2;
633 } else if (world.gametype == "c3") {
634 if (gamename.empty()) gamename = "Creatures 3";
635 version = 3;
636 } else if (world.gametype == "cv") {
637 if (gamename.empty()) gamename = "Creatures Village";
638 version = 3;
639 world.autostop = !world.autostop;
640 } else if (world.gametype == "sm") {
641 if (gamename.empty()) gamename = "Sea-Monkeys";
642 version = 3;
643 bmprenderer = true;
644 } else
645 throw creaturesException(boost::str(boost::format("unknown gametype '%s'!") % world.gametype));
647 // finally, add our cache directory to the end
648 world.data_directories.push_back(storageDirectory());
650 // initial setup
651 registerDelegates();
652 std::cout << "* Reading catalogue files..." << std::endl;
653 world.initCatalogue();
654 std::cout << "* Initial setup..." << std::endl;
655 world.init(); // just reads mouse cursor (we want this after the catalogue reading so we don't play "guess the filename")
656 if (engine.version > 2) {
657 std::cout << "* Reading PRAY files..." << std::endl;
658 world.praymanager.update();
661 #ifdef _WIN32
662 // Here we need to set the working directory since apparently windows != clever
663 char exepath[MAX_PATH] = "";
664 GetModuleFileName(0, exepath, sizeof(exepath) - 1);
665 char *exedir = strrchr(exepath, '\\');
666 if(exedir) {
667 // null terminate the string
668 *exedir = 0;
669 // Set working directory
670 SetCurrentDirectory(exepath);
672 else // err, oops
673 std::cerr << "Warning: Setting working directory to " << exepath << " failed.";
674 #endif
676 if (cmdline_norun) preferred_backend = "null";
677 if (preferred_backend != "null") std::cout << "* Initialising backend " << preferred_backend << "..." << std::endl;
678 shared_ptr<Backend> b = possible_backends[preferred_backend];
679 if (!b) throw creaturesException("No such backend " + preferred_backend);
680 b->init(); setBackend(b);
681 possible_backends.clear();
683 if (cmdline_norun || !cmdline_enable_sound) preferred_audiobackend = "null";
684 if (preferred_audiobackend != "null") std::cout << "* Initialising audio backend " << preferred_audiobackend << "..." << std::endl;
685 shared_ptr<AudioBackend> a = possible_audiobackends[preferred_audiobackend];
686 if (!a) throw creaturesException("No such audio backend " + preferred_audiobackend);
687 try{
688 a->init(); audio = a;
689 } catch (creaturesException &e) {
690 std::cerr << "* Couldn't initialize backend " << preferred_audiobackend << ": " << e.what() << std::endl << "* Continuing without sound." << std::endl;
691 audio = shared_ptr<AudioBackend>(new NullAudioBackend());
692 audio->init();
694 possible_audiobackends.clear();
696 world.camera.setBackend(backend); // TODO: hrr
698 int listenport = backend->networkInit();
699 if (listenport != -1) {
700 // inform the user of the port used, and store it in the relevant file
701 std::cout << "Listening for connections on port " << listenport << "." << std::endl;
702 #ifndef _WIN32
703 fs::path p = fs::path(homeDirectory().native_directory_string() + "/.creaturesengine", fs::native);
704 if (!fs::exists(p))
705 fs::create_directory(p);
706 if (fs::is_directory(p)) {
707 std::ofstream f((p.native_directory_string() + "/port").c_str(), std::ios::trunc);
708 f << boost::str(boost::format("%d") % listenport);
710 #endif
713 if (world.data_directories.size() < 3) {
714 // TODO: This is a hack for DS, basically. Not sure if it works properly. - fuzzie
715 caosVar name; name.setString("engine_no_auxiliary_bootstrap_1");
716 caosVar contents; contents.setInt(1);
717 eame_variables[name] = contents;
720 // execute the initial scripts!
721 std::cout << "* Executing initial scripts..." << std::endl;
722 if (cmdline_bootstrap.size() == 0) {
723 world.executeBootstrap(false);
724 } else {
725 std::vector<std::string> scripts;
727 if (engine.version < 3 && cmdline_bootstrap.size() != 1)
728 throw creaturesException("multiple bootstrap files provided in C1/C2 mode");
730 for (std::vector< std::string >::iterator bsi = cmdline_bootstrap.begin(); bsi != cmdline_bootstrap.end(); bsi++) {
731 fs::path scriptdir(*bsi, fs::native);
732 if (engine.version > 2 || fs::extension(scriptdir) == ".cos") {
733 // pass it to the world to execute (it handles both files and directories)
735 if (!fs::exists(scriptdir)) {
736 std::cerr << "Warning: Couldn't find a specified script directory (trying " << *bsi << ")!\n";
737 continue;
740 world.executeBootstrap(scriptdir);
741 } else {
742 // in c1/c2 mode, if not a cos file, assume it's an SFC file
743 if (!fs::exists(scriptdir) || fs::is_directory(scriptdir))
744 throw creaturesException("non-existant bootstrap file provided in C1/C2 mode");
745 // TODO: the default SFCFile loading code is in World, maybe this should be too..
746 SFCFile sfc;
747 std::ifstream f(scriptdir.native_directory_string().c_str(), std::ios::binary);
748 f >> std::noskipws;
749 sfc.read(&f);
750 sfc.copyToWorld();
755 // if there aren't any metarooms, we can't run a useful game, the user probably
756 // wanted to execute a CAOS script or something went badly wrong.
757 if (!cmdline_norun && world.map.getMetaRoomCount() == 0) {
758 shutdown();
759 throw creaturesException("No metarooms found in given bootstrap directories or files");
762 std::cout << "* Done startup." << std::endl;
764 if (cmdline_norun) {
765 // TODO: see comment above about avoiding backend when norun is set
766 std::cout << "Told not to run the world, so stopping now." << std::endl;
767 shutdown();
768 return false;
771 return true;
774 void Engine::shutdown() {
775 world.shutdown();
776 backend->shutdown();
777 audio->shutdown();
778 freeDelegates(); // does nothing if there are none (ie, no call to initialSetup)
781 fs::path Engine::homeDirectory() {
782 fs::path p;
784 #ifndef _WIN32
785 char *envhome = getenv("HOME");
786 if (envhome)
787 p = fs::path(envhome, fs::native);
788 if ((!envhome) || (!fs::is_directory(p)))
789 p = fs::path(getpwuid(getuid())->pw_dir, fs::native);
790 if (!fs::is_directory(p)) {
791 std::cerr << "Can't work out what your home directory is, giving up and using /tmp for now." << std::endl;
792 p = fs::path("/tmp", fs::native); // sigh
794 #else
795 TCHAR szPath[_MAX_PATH];
796 SHGetSpecialFolderPath(NULL, szPath, CSIDL_PERSONAL, TRUE);
798 p = fs::path(szPath, fs::native);
799 if (!fs::exists(p) || !fs::is_directory(p))
800 throw creaturesException("Windows reported that your My Documents folder is at '" + std::string(szPath) + "' but there's no directory there!");
801 #endif
803 return p;
806 fs::path Engine::storageDirectory() {
807 #ifndef _WIN32
808 std::string dirname = "/.openc2e";
809 #else
810 std::string dirname = "/My Games";
811 #endif
813 // main storage dir
814 fs::path p = fs::path(homeDirectory().native_directory_string() + dirname, fs::native);
815 if (!fs::exists(p))
816 fs::create_directory(p);
817 else if (!fs::is_directory(p))
818 throw creaturesException("Your openc2e data directory " + p.native_directory_string() + " is a file, not a directory. That's bad.");
820 // game-specific storage dir
821 p = fs::path(p.native_directory_string() + std::string("/" + gamename), fs::native);
822 if (!fs::exists(p))
823 fs::create_directory(p);
824 else if (!fs::is_directory(p))
825 throw creaturesException("Your openc2e game data directory " + p.native_directory_string() + " is a file, not a directory. That's bad.");
827 return p;
830 /* vim: set noet: */