1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * Additional copyright for this file:
8 * Copyright (C) 1994-1998 Revolution Software Ltd.
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version 2
13 * of the License, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 */
24
25 #include "base/plugins.h"
26
27 #include "common/config-manager.h"
28 #include "common/events.h"
29 #include "common/file.h"
30 #include "common/fs.h"
31 #include "common/gui_options.h"
32 #include "common/savefile.h"
33 #include "common/system.h"
34 #include "common/textconsole.h"
35 #include "common/translation.h"
36
37 #include "engines/metaengine.h"
38 #include "engines/util.h"
39
40 #include "sword2/sword2.h"
41 #include "sword2/defs.h"
42 #include "sword2/header.h"
43 #include "sword2/console.h"
44 #include "sword2/controls.h"
45 #include "sword2/logic.h"
46 #include "sword2/maketext.h"
47 #include "sword2/memory.h"
48 #include "sword2/mouse.h"
49 #include "sword2/resman.h"
50 #include "sword2/router.h"
51 #include "sword2/screen.h"
52 #include "sword2/sound.h"
53 #include "sword2/saveload.h"
54
55 namespace Sword2 {
56
57 Common::Platform Sword2Engine::_platform;
58
59 struct GameSettings {
60 const char *gameid;
61 const char *description;
62 uint32 features;
63 const char *detectname;
64 };
65
66 static const GameSettings sword2_settings[] = {
67 /* Broken Sword II */
68 {"sword2", "Broken Sword II: The Smoking Mirror", 0, "players.clu" },
69 {"sword2alt", "Broken Sword II: The Smoking Mirror (alt)", 0, "r2ctlns.ocx" },
70 {"sword2psx", "Broken Sword II: The Smoking Mirror (PlayStation)", 0, "screens.clu"},
71 {"sword2psxdemo", "Broken Sword II: The Smoking Mirror (PlayStation/Demo)", Sword2::GF_DEMO, "screens.clu"},
72 {"sword2demo", "Broken Sword II: The Smoking Mirror (Demo)", Sword2::GF_DEMO, "players.clu" },
73 {NULL, NULL, 0, NULL}
74 };
75
76 } // End of namespace Sword2
77
78 static const ExtraGuiOption sword2ExtraGuiOption = {
79 _s("Show object labels"),
80 _s("Show labels for objects on mouse hover"),
81 "object_labels",
82 false
83 };
84
85 class Sword2MetaEngine : public MetaEngine {
86 public:
getName() const87 virtual const char *getName() const {
88 return "Broken Sword II: The Smoking Mirror";
89 }
getOriginalCopyright() const90 virtual const char *getOriginalCopyright() const {
91 return "Broken Sword II: The Smoking Mirror (C) Revolution";
92 }
93
94 virtual bool hasFeature(MetaEngineFeature f) const;
95 PlainGameList getSupportedGames() const override;
96 virtual const ExtraGuiOptions getExtraGuiOptions(const Common::String &target) const;
97 PlainGameDescriptor findGame(const char *gameid) const override;
98 virtual DetectedGames detectGames(const Common::FSList &fslist) const;
99 virtual SaveStateList listSaves(const char *target) const;
100 virtual int getMaximumSaveSlot() const;
101 virtual void removeSaveState(const char *target, int slot) const;
102
103 virtual Common::Error createInstance(OSystem *syst, Engine **engine) const;
104 };
105
hasFeature(MetaEngineFeature f) const106 bool Sword2MetaEngine::hasFeature(MetaEngineFeature f) const {
107 return
108 (f == kSupportsListSaves) ||
109 (f == kSupportsLoadingDuringStartup) ||
110 (f == kSupportsDeleteSave) ||
111 (f == kSimpleSavesNames);
112 }
113
hasFeature(EngineFeature f) const114 bool Sword2::Sword2Engine::hasFeature(EngineFeature f) const {
115 return
116 (f == kSupportsRTL) ||
117 (f == kSupportsSubtitleOptions) ||
118 (f == kSupportsSavingDuringRuntime) ||
119 (f == kSupportsLoadingDuringRuntime);
120 }
121
getSupportedGames() const122 PlainGameList Sword2MetaEngine::getSupportedGames() const {
123 const Sword2::GameSettings *g = Sword2::sword2_settings;
124 PlainGameList games;
125 while (g->gameid) {
126 games.push_back(PlainGameDescriptor::of(g->gameid, g->description));
127 g++;
128 }
129 return games;
130 }
131
getExtraGuiOptions(const Common::String & target) const132 const ExtraGuiOptions Sword2MetaEngine::getExtraGuiOptions(const Common::String &target) const {
133 ExtraGuiOptions options;
134 options.push_back(sword2ExtraGuiOption);
135 return options;
136 }
137
findGame(const char * gameid) const138 PlainGameDescriptor Sword2MetaEngine::findGame(const char *gameid) const {
139 const Sword2::GameSettings *g = Sword2::sword2_settings;
140 while (g->gameid) {
141 if (0 == scumm_stricmp(gameid, g->gameid))
142 break;
143 g++;
144 }
145 return PlainGameDescriptor::of(g->gameid, g->description);
146 }
147
isFullGame(const Common::FSList & fslist)148 bool isFullGame(const Common::FSList &fslist) {
149 Common::FSList::const_iterator file;
150
151 // We distinguish between the two versions by the presence of paris.clu
152 for (file = fslist.begin(); file != fslist.end(); ++file) {
153 if (!file->isDirectory()) {
154 if (file->getName().equalsIgnoreCase("paris.clu"))
155 return true;
156 }
157 }
158
159 return false;
160 }
161
detectGamesImpl(const Common::FSList & fslist,bool recursion=false)162 DetectedGames detectGamesImpl(const Common::FSList &fslist, bool recursion = false) {
163 DetectedGames detectedGames;
164 const Sword2::GameSettings *g;
165 Common::FSList::const_iterator file;
166 bool isFullVersion = isFullGame(fslist);
167
168 for (g = Sword2::sword2_settings; g->gameid; ++g) {
169 // Iterate over all files in the given directory
170 for (file = fslist.begin(); file != fslist.end(); ++file) {
171 if (!file->isDirectory()) {
172 // The required game data files can be located in the game directory, or in
173 // a subdirectory called "clusters". In the latter case, we don't want to
174 // detect the game in that subdirectory, as this will detect the game twice
175 // when mass add is searching inside a directory. In this case, the first
176 // result (the game directory) will be correct, but the second result (the
177 // clusters subdirectory) will be wrong, as the optional speech, music and
178 // video data files will be ignored. Note that this fix will skip the game
179 // data files if the user has placed them inside a "clusters" subdirectory,
180 // or if he/she points ScummVM directly to the "clusters" directory of the
181 // game CD. Fixes bug #3049336.
182 Common::String directory = file->getParent().getName();
183 directory.toLowercase();
184 if (directory.hasPrefix("clusters") && directory.size() <= 9 && !recursion)
185 continue;
186
187 if (file->getName().equalsIgnoreCase(g->detectname)) {
188 // Make sure that the sword2 demo is not mixed up with the
189 // full version, since they use the same filename for detection
190 if ((g->features == Sword2::GF_DEMO && isFullVersion) ||
191 (g->features == 0 && !isFullVersion))
192 continue;
193
194 // Match found, add to list of candidates, then abort inner loop.
195 DetectedGame game = DetectedGame(g->gameid, g->description);
196 game.setGUIOptions(GUIO2(GUIO_NOMIDI, GUIO_NOASPECT));
197
198 detectedGames.push_back(game);
199 break;
200 }
201 }
202 }
203 }
204
205
206 if (detectedGames.empty()) {
207 // Nothing found -- try to recurse into the 'clusters' subdirectory,
208 // present e.g. if the user copied the data straight from CD.
209 for (file = fslist.begin(); file != fslist.end(); ++file) {
210 if (file->isDirectory()) {
211 if (file->getName().equalsIgnoreCase("clusters")) {
212 Common::FSList recList;
213 if (file->getChildren(recList, Common::FSNode::kListAll)) {
214 DetectedGames recGames = detectGamesImpl(recList, true);
215 if (!recGames.empty()) {
216 detectedGames.push_back(recGames);
217 break;
218 }
219 }
220 }
221 }
222 }
223 }
224
225
226 return detectedGames;
227 }
228
detectGames(const Common::FSList & fslist) const229 DetectedGames Sword2MetaEngine::detectGames(const Common::FSList &fslist) const {
230 return detectGamesImpl(fslist);
231 }
232
listSaves(const char * target) const233 SaveStateList Sword2MetaEngine::listSaves(const char *target) const {
234 Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
235 Common::StringArray filenames;
236 char saveDesc[SAVE_DESCRIPTION_LEN];
237 Common::String pattern = target;
238 pattern += ".###";
239
240 filenames = saveFileMan->listSavefiles(pattern);
241
242 SaveStateList saveList;
243 for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
244 // Obtain the last 3 digits of the filename, since they correspond to the save slot
245 int slotNum = atoi(file->c_str() + file->size() - 3);
246
247 if (slotNum >= 0 && slotNum <= 999) {
248 Common::InSaveFile *in = saveFileMan->openForLoading(*file);
249 if (in) {
250 in->readUint32LE();
251 in->read(saveDesc, SAVE_DESCRIPTION_LEN);
252 saveList.push_back(SaveStateDescriptor(slotNum, saveDesc));
253 delete in;
254 }
255 }
256 }
257
258 // Sort saves based on slot number.
259 Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
260 return saveList;
261 }
262
getMaximumSaveSlot() const263 int Sword2MetaEngine::getMaximumSaveSlot() const { return 999; }
264
removeSaveState(const char * target,int slot) const265 void Sword2MetaEngine::removeSaveState(const char *target, int slot) const {
266 Common::String filename = target;
267 filename += Common::String::format(".%03d", slot);
268
269 g_system->getSavefileManager()->removeSavefile(filename);
270 }
271
createInstance(OSystem * syst,Engine ** engine) const272 Common::Error Sword2MetaEngine::createInstance(OSystem *syst, Engine **engine) const {
273 assert(syst);
274 assert(engine);
275
276 Common::FSList fslist;
277 Common::FSNode dir(ConfMan.get("path"));
278 if (!dir.getChildren(fslist, Common::FSNode::kListAll)) {
279 return Common::kNoGameDataFoundError;
280 }
281
282 // Invoke the detector
283 Common::String gameid = ConfMan.get("gameid");
284 DetectedGames detectedGames = detectGames(fslist);
285
286 for (uint i = 0; i < detectedGames.size(); i++) {
287 if (detectedGames[i].gameId == gameid) {
288 *engine = new Sword2::Sword2Engine(syst);
289 return Common::kNoError;
290 }
291 }
292
293 return Common::kNoGameDataFoundError;
294 }
295
296 #if PLUGIN_ENABLED_DYNAMIC(SWORD2)
297 REGISTER_PLUGIN_DYNAMIC(SWORD2, PLUGIN_TYPE_ENGINE, Sword2MetaEngine);
298 #else
299 REGISTER_PLUGIN_STATIC(SWORD2, PLUGIN_TYPE_ENGINE, Sword2MetaEngine);
300 #endif
301
302 namespace Sword2 {
303
Sword2Engine(OSystem * syst)304 Sword2Engine::Sword2Engine(OSystem *syst) : Engine(syst), _rnd("sword2") {
305 // Add default file directories
306 const Common::FSNode gameDataDir(ConfMan.get("path"));
307 SearchMan.addSubDirectoryMatching(gameDataDir, "clusters");
308 SearchMan.addSubDirectoryMatching(gameDataDir, "sword2");
309 SearchMan.addSubDirectoryMatching(gameDataDir, "video");
310 SearchMan.addSubDirectoryMatching(gameDataDir, "smacks");
311 SearchMan.addSubDirectoryMatching(gameDataDir, "streams"); // PSX video
312
313 if (!scumm_stricmp(ConfMan.get("gameid").c_str(), "sword2demo") || !scumm_stricmp(ConfMan.get("gameid").c_str(), "sword2psxdemo"))
314 _features = GF_DEMO;
315 else
316 _features = 0;
317
318 // Check if we are running PC or PSX version.
319 if (!scumm_stricmp(ConfMan.get("gameid").c_str(), "sword2psx") || !scumm_stricmp(ConfMan.get("gameid").c_str(), "sword2psxdemo"))
320 Sword2Engine::_platform = Common::kPlatformPSX;
321 else
322 Sword2Engine::_platform = Common::kPlatformWindows;
323
324 _bootParam = ConfMan.getInt("boot_param");
325 _saveSlot = ConfMan.getInt("save_slot");
326
327 _memory = NULL;
328 _resman = NULL;
329 _sound = NULL;
330 _screen = NULL;
331 _mouse = NULL;
332 _logic = NULL;
333 _fontRenderer = NULL;
334 _debugger = NULL;
335
336 _keyboardEvent.pending = false;
337 _mouseEvent.pending = false;
338
339 _wantSfxDebug = false;
340
341 _gameCycle = 0;
342 _gameSpeed = 1;
343
344 _gmmLoadSlot = -1; // Used to manage GMM Loading
345 }
346
~Sword2Engine()347 Sword2Engine::~Sword2Engine() {
348 delete _debugger;
349 delete _sound;
350 delete _fontRenderer;
351 delete _screen;
352 delete _mouse;
353 delete _logic;
354 delete _resman;
355 delete _memory;
356 }
357
getDebugger()358 GUI::Debugger *Sword2Engine::getDebugger() {
359 return _debugger;
360 }
361
registerDefaultSettings()362 void Sword2Engine::registerDefaultSettings() {
363 ConfMan.registerDefault("gfx_details", 2);
364 ConfMan.registerDefault("reverse_stereo", false);
365 }
366
syncSoundSettings()367 void Sword2Engine::syncSoundSettings() {
368 Engine::syncSoundSettings();
369
370 bool mute = ConfMan.getBool("mute");
371
372 setSubtitles(ConfMan.getBool("subtitles"));
373
374 // Our own settings dialog can mute the music, speech and sound effects
375 // individually. ScummVM's settings dialog has one master mute setting.
376
377 if (ConfMan.hasKey("mute")) {
378 ConfMan.setBool("music_mute", ConfMan.getBool("mute"));
379 ConfMan.setBool("speech_mute", ConfMan.getBool("mute"));
380 ConfMan.setBool("sfx_mute", ConfMan.getBool("mute"));
381
382 if (!mute) // it is false
383 // So remove it in order to let individual volumes work
384 ConfMan.removeKey("mute", ConfMan.getActiveDomainName());
385 }
386
387 _sound->muteMusic(ConfMan.getBool("music_mute"));
388 _sound->muteSpeech(ConfMan.getBool("speech_mute"));
389 _sound->muteFx(ConfMan.getBool("sfx_mute"));
390 _sound->setReverseStereo(ConfMan.getBool("reverse_stereo"));
391 }
392
readSettings()393 void Sword2Engine::readSettings() {
394 syncSoundSettings();
395 _mouse->setObjectLabels(ConfMan.getBool("object_labels"));
396 _screen->setRenderLevel(ConfMan.getInt("gfx_details"));
397 }
398
writeSettings()399 void Sword2Engine::writeSettings() {
400 ConfMan.setInt("music_volume", _mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType));
401 ConfMan.setInt("speech_volume", _mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType));
402 ConfMan.setInt("sfx_volume", _mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType));
403 ConfMan.setBool("music_mute", _sound->isMusicMute());
404 ConfMan.setBool("speech_mute", _sound->isSpeechMute());
405 ConfMan.setBool("sfx_mute", _sound->isFxMute());
406 ConfMan.setInt("gfx_details", _screen->getRenderLevel());
407 ConfMan.setBool("subtitles", getSubtitles());
408 ConfMan.setBool("object_labels", _mouse->getObjectLabels());
409 ConfMan.setInt("reverse_stereo", _sound->isReverseStereo());
410
411 // If even one sound type is unmuted, we can't say that all sound is
412 // muted.
413
414 if (!_sound->isMusicMute() || !_sound->isSpeechMute() || !_sound->isFxMute()) {
415 ConfMan.setBool("mute", false);
416 }
417
418 ConfMan.flushToDisk();
419 }
420
getFramesPerSecond()421 int Sword2Engine::getFramesPerSecond() {
422 return _gameSpeed * FRAMES_PER_SECOND;
423 }
424
425 /**
426 * The global script variables and player object should be kept open throughout
427 * the game, so that they are never expelled by the resource manager.
428 */
429
setupPersistentResources()430 void Sword2Engine::setupPersistentResources() {
431 _logic->_scriptVars = _resman->openResource(1) + ResHeader::size();
432 _resman->openResource(CUR_PLAYER_ID);
433 }
434
run()435 Common::Error Sword2Engine::run() {
436 // Get some falling RAM and put it in your pocket, never let it slip
437 // away
438
439 _debugger = NULL;
440 _sound = NULL;
441 _fontRenderer = NULL;
442 _screen = NULL;
443 _mouse = NULL;
444 _logic = NULL;
445 _resman = NULL;
446 _memory = NULL;
447
448 initGraphics(640, 480);
449 _screen = new Screen(this, 640, 480);
450
451 // Create the debugger as early as possible (but not before the
452 // screen object!) so that errors can be displayed in it. In
453 // particular, we want errors about missing files to be clearly
454 // visible to the user.
455
456 _debugger = new Debugger(this);
457
458 _memory = new MemoryManager();
459 _resman = new ResourceManager(this);
460
461 if (!_resman->init())
462 return Common::kUnknownError;
463
464 _logic = new Logic(this);
465 _fontRenderer = new FontRenderer(this);
466 _sound = new Sound(this);
467 _mouse = new Mouse(this);
468
469 registerDefaultSettings();
470 readSettings();
471
472 initStartMenu();
473
474 // During normal gameplay, we care neither about mouse button releases
475 // nor the scroll wheel.
476 setInputEventFilter(RD_LEFTBUTTONUP | RD_RIGHTBUTTONUP | RD_WHEELUP | RD_WHEELDOWN);
477
478 setupPersistentResources();
479 initializeFontResourceFlags();
480
481 if (_features & GF_DEMO)
482 _logic->writeVar(DEMO, 1);
483 else
484 _logic->writeVar(DEMO, 0);
485
486 if (_saveSlot != -1) {
487 if (saveExists(_saveSlot))
488 restoreGame(_saveSlot);
489 else {
490 RestoreDialog dialog(this);
491 if (!dialog.runModal())
492 startGame();
493 }
494 } else if (!_bootParam && saveExists() && !isPsx()) { // Initial load/restart panel disabled in PSX
495 int32 pars[2] = { 221, FX_LOOP }; // version because of missing panel resources
496 bool result;
497
498 _mouse->setMouse(NORMAL_MOUSE_ID);
499 _logic->fnPlayMusic(pars);
500
501 StartDialog dialog(this);
502
503 result = (dialog.runModal() != 0);
504
505 // If the game is started from the beginning, the cutscene
506 // player will kill the music for us. Otherwise, the restore
507 // will either have killed the music, or done a crossfade.
508
509 if (shouldQuit())
510 return Common::kNoError;
511
512 if (result)
513 startGame();
514 } else
515 startGame();
516
517 _screen->initializeRenderCycle();
518
519 while (1) {
520 _debugger->onFrame();
521
522 // Handle GMM Loading
523 if (_gmmLoadSlot != -1) {
524
525 // Hide mouse cursor and fade screen
526 _mouse->hideMouse();
527 _screen->fadeDown();
528
529 // Clean up and load game
530 _logic->_router->freeAllRouteMem();
531
532 // TODO: manage error handling
533 restoreGame(_gmmLoadSlot);
534
535 // Reset load slot
536 _gmmLoadSlot = -1;
537
538 // Show mouse
539 _mouse->addHuman();
540 }
541
542 KeyboardEvent *ke = keyboardEvent();
543
544 if (ke) {
545 if ((ke->kbd.hasFlags(Common::KBD_CTRL) && ke->kbd.keycode == Common::KEYCODE_d) || ke->kbd.ascii == '#' || ke->kbd.ascii == '~') {
546 _debugger->attach();
547 } else if (ke->kbd.hasFlags(0) || ke->kbd.hasFlags(Common::KBD_SHIFT)) {
548 switch (ke->kbd.keycode) {
549 case Common::KEYCODE_p:
550 if (isPaused()) {
551 _screen->dimPalette(false);
552 pauseEngine(false);
553 } else {
554 pauseEngine(true);
555 _screen->dimPalette(true);
556 }
557 break;
558 #if 0
559 // Disabled because of strange rumors about the
560 // credits running spontaneously every few
561 // minutes.
562 case Common::KEYCODE_c:
563 if (!_logic->readVar(DEMO) && !_mouse->isChoosing()) {
564 ScreenInfo *screenInfo = _screen->getScreenInfo();
565 _logic->fnPlayCredits(NULL);
566 screenInfo->new_palette = 99;
567 }
568 break;
569 #endif
570 default:
571 break;
572 }
573 }
574 }
575
576 // skip GameCycle if we're paused
577 if (!isPaused()) {
578 _gameCycle++;
579 gameCycle();
580 }
581
582 // We can't use this as termination condition for the loop,
583 // because we want the break to happen before updating the
584 // screen again.
585
586 if (shouldQuit())
587 break;
588
589 // creates the debug text blocks
590 _debugger->buildDebugText();
591
592 _screen->buildDisplay();
593 }
594
595 return Common::kNoError;
596 }
597
restartGame()598 void Sword2Engine::restartGame() {
599 ScreenInfo *screenInfo = _screen->getScreenInfo();
600 uint32 temp_demo_flag;
601
602 _mouse->closeMenuImmediately();
603
604 // Restart the game. To do this, we must...
605
606 // Stop music instantly!
607 _sound->stopMusic(true);
608
609 // In case we were dead - well we're not anymore!
610 _logic->writeVar(DEAD, 0);
611
612 // Restart the game. Clear all memory and reset the globals
613 temp_demo_flag = _logic->readVar(DEMO);
614
615 // Remove all resources from memory, including player object and
616 // global variables
617 _resman->removeAll();
618
619 // Reopen global variables resource and player object
620 setupPersistentResources();
621
622 _logic->writeVar(DEMO, temp_demo_flag);
623
624 // Free all the route memory blocks from previous game
625 _logic->_router->freeAllRouteMem();
626
627 // Call the same function that first started us up
628 startGame();
629
630 // Prime system with a game cycle
631
632 // Reset the graphic 'BuildUnit' list before a new logic list
633 // (see fnRegisterFrame)
634 _screen->resetRenderLists();
635
636 // Reset the mouse hot-spot list (see fnRegisterMouse and
637 // fnRegisterFrame)
638 _mouse->resetMouseList();
639
640 _mouse->closeMenuImmediately();
641
642 // FOR THE DEMO - FORCE THE SCROLLING TO BE RESET!
643 // - this is taken from fnInitBackground
644 // switch on scrolling (2 means first time on screen)
645 screenInfo->scroll_flag = 2;
646
647 if (_logic->processSession())
648 error("restart 1st cycle failed??");
649
650 // So palette not restored immediately after control panel - we want
651 // to fade up instead!
652 screenInfo->new_palette = 99;
653 }
654
checkForMouseEvents()655 bool Sword2Engine::checkForMouseEvents() {
656 return _mouseEvent.pending;
657 }
658
mouseEvent()659 MouseEvent *Sword2Engine::mouseEvent() {
660 if (!_mouseEvent.pending)
661 return NULL;
662
663 _mouseEvent.pending = false;
664 return &_mouseEvent;
665 }
666
keyboardEvent()667 KeyboardEvent *Sword2Engine::keyboardEvent() {
668 if (!_keyboardEvent.pending)
669 return NULL;
670
671 _keyboardEvent.pending = false;
672 return &_keyboardEvent;
673 }
674
setInputEventFilter(uint32 filter)675 uint32 Sword2Engine::setInputEventFilter(uint32 filter) {
676 uint32 oldFilter = _inputEventFilter;
677
678 _inputEventFilter = filter;
679 return oldFilter;
680 }
681
682 /**
683 * OSystem Event Handler. Full of cross platform goodness and 99% fat free!
684 */
685
parseInputEvents()686 void Sword2Engine::parseInputEvents() {
687 Common::Event event;
688
689 while (_eventMan->pollEvent(event)) {
690 switch (event.type) {
691 case Common::EVENT_KEYDOWN:
692 if (event.kbd.hasFlags(Common::KBD_CTRL)) {
693 if (event.kbd.keycode == Common::KEYCODE_f) {
694 if (_gameSpeed == 1)
695 _gameSpeed = 2;
696 else
697 _gameSpeed = 1;
698 }
699 }
700 if (!(_inputEventFilter & RD_KEYDOWN)) {
701 _keyboardEvent.pending = true;
702 _keyboardEvent.kbd = event.kbd;
703 }
704 break;
705 case Common::EVENT_LBUTTONDOWN:
706 if (!(_inputEventFilter & RD_LEFTBUTTONDOWN)) {
707 _mouseEvent.pending = true;
708 _mouseEvent.buttons = RD_LEFTBUTTONDOWN;
709 }
710 break;
711 case Common::EVENT_RBUTTONDOWN:
712 if (!(_inputEventFilter & RD_RIGHTBUTTONDOWN)) {
713 _mouseEvent.pending = true;
714 _mouseEvent.buttons = RD_RIGHTBUTTONDOWN;
715 }
716 break;
717 case Common::EVENT_LBUTTONUP:
718 if (!(_inputEventFilter & RD_LEFTBUTTONUP)) {
719 _mouseEvent.pending = true;
720 _mouseEvent.buttons = RD_LEFTBUTTONUP;
721 }
722 break;
723 case Common::EVENT_RBUTTONUP:
724 if (!(_inputEventFilter & RD_RIGHTBUTTONUP)) {
725 _mouseEvent.pending = true;
726 _mouseEvent.buttons = RD_RIGHTBUTTONUP;
727 }
728 break;
729 case Common::EVENT_WHEELUP:
730 if (!(_inputEventFilter & RD_WHEELUP)) {
731 _mouseEvent.pending = true;
732 _mouseEvent.buttons = RD_WHEELUP;
733 }
734 break;
735 case Common::EVENT_WHEELDOWN:
736 if (!(_inputEventFilter & RD_WHEELDOWN)) {
737 _mouseEvent.pending = true;
738 _mouseEvent.buttons = RD_WHEELDOWN;
739 }
740 break;
741 default:
742 break;
743 }
744 }
745 }
746
gameCycle()747 void Sword2Engine::gameCycle() {
748 // Do one game cycle, that is run the logic session until a full loop
749 // has been performed.
750
751 if (_logic->getRunList()) {
752 do {
753 // Reset the 'BuildUnit' and mouse hot-spot lists
754 // before each new logic list. The service scripts
755 // will fill thrm through fnRegisterFrame() and
756 // fnRegisterMouse().
757
758 _screen->resetRenderLists();
759 _mouse->resetMouseList();
760
761 // Keep going as long as new lists keep getting put in
762 // - i.e. screen changes.
763 } while (_logic->processSession());
764 } else {
765 // Start the console and print the start options perhaps?
766 _debugger->attach("AWAITING START COMMAND: (Enter 's 1' then 'q' to start from beginning)");
767 }
768
769 // If this screen is wide, recompute the scroll offsets every cycle
770 ScreenInfo *screenInfo = _screen->getScreenInfo();
771
772 if (screenInfo->scroll_flag)
773 _screen->setScrolling();
774
775 _mouse->mouseEngine();
776 _sound->processFxQueue();
777 }
778
startGame()779 void Sword2Engine::startGame() {
780 // Boot the game straight into a start script. It's always George's
781 // script #1, but with different ScreenManager objects depending on
782 // if it's the demo or the full game, or if we're using a boot param.
783
784 int screen_manager_id = 0;
785
786 debug(5, "startGame() STARTING:");
787
788 if (!_bootParam) {
789 if (_logic->readVar(DEMO))
790 screen_manager_id = 19; // DOCKS SECTION START
791 else
792 screen_manager_id = 949; // INTRO & PARIS START
793 } else {
794 // FIXME this could be validated against startup.inf for valid
795 // numbers to stop people shooting themselves in the foot
796
797 if (_bootParam != 0)
798 screen_manager_id = _bootParam;
799 }
800
801 _logic->runResObjScript(screen_manager_id, CUR_PLAYER_ID, 1);
802 }
803
804 // FIXME: Move this to some better place?
805
sleepUntil(uint32 time)806 void Sword2Engine::sleepUntil(uint32 time) {
807 while (getMillis() < time) {
808 // Make sure menu animations and fades don't suffer, but don't
809 // redraw the entire scene.
810 _mouse->processMenu();
811 _screen->updateDisplay(false);
812 _system->delayMillis(10);
813 }
814 }
815
pauseEngineIntern(bool pause)816 void Sword2Engine::pauseEngineIntern(bool pause) {
817 Engine::pauseEngineIntern(pause);
818
819 if (pause) {
820 _screen->pauseScreen(true);
821 } else {
822 _screen->pauseScreen(false);
823 }
824 }
825
getMillis()826 uint32 Sword2Engine::getMillis() {
827 return _system->getMillis();
828 }
829
saveGameState(int slot,const Common::String & desc)830 Common::Error Sword2Engine::saveGameState(int slot, const Common::String &desc) {
831 uint32 saveVal = saveGame(slot, (const byte *)desc.c_str());
832
833 if (saveVal == SR_OK)
834 return Common::kNoError;
835 else if (saveVal == SR_ERR_WRITEFAIL || saveVal == SR_ERR_FILEOPEN)
836 return Common::kWritingFailed;
837 else
838 return Common::kUnknownError;
839 }
840
canSaveGameStateCurrently()841 bool Sword2Engine::canSaveGameStateCurrently() {
842 bool canSave = true;
843
844 // No save if dead
845 if (_logic->readVar(DEAD))
846 canSave = false;
847
848 // No save if mouse not shown
849 else if (_mouse->getMouseStatus())
850 canSave = false;
851 // No save if inside a menu
852 else if (_mouse->getMouseMode() == MOUSE_system_menu)
853 canSave = false;
854
855 // No save if fading
856 else if (_screen->getFadeStatus())
857 canSave = false;
858
859 return canSave;
860 }
861
loadGameState(int slot)862 Common::Error Sword2Engine::loadGameState(int slot) {
863
864 // Prepare the game to load through GMM
865 _gmmLoadSlot = slot;
866
867 // TODO: error handling.
868 return Common::kNoError;
869 }
870
canLoadGameStateCurrently()871 bool Sword2Engine::canLoadGameStateCurrently() {
872 bool canLoad = true;
873
874 // No load if mouse is disabled
875 if (_mouse->getMouseStatus())
876 canLoad = false;
877 // No load if mouse is in system menu
878 else if (_mouse->getMouseMode() == MOUSE_system_menu)
879 canLoad = false;
880 // No load if we are fading
881 else if (_screen->getFadeStatus())
882 canLoad = false;
883
884 // But if we are dead, ignore previous conditions
885 if (_logic->readVar(DEAD))
886 canLoad = true;
887
888 return canLoad;
889 }
890
891 } // End of namespace Sword2
892