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