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  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "common/scummsys.h"
24 
25 #include "common/config-manager.h"
26 #include "common/debug-channels.h"
27 #include "common/events.h"
28 #include "common/file.h"
29 #include "common/keyboard.h"
30 
31 #include "engines/util.h"
32 
33 #include "graphics/cursorman.h"
34 #include "graphics/font.h"
35 
36 #include "draci/draci.h"
37 #include "draci/animation.h"
38 #include "draci/barchive.h"
39 #include "draci/font.h"
40 #include "draci/game.h"
41 #include "draci/mouse.h"
42 #include "draci/music.h"
43 #include "draci/saveload.h"
44 #include "draci/screen.h"
45 #include "draci/script.h"
46 #include "draci/sound.h"
47 #include "draci/sprite.h"
48 
49 namespace Draci {
50 
51 // Data file paths
52 
53 const char *objectsPath = "OBJEKTY.DFW";
54 const char *palettePath = "PALETY.DFW";
55 const char *spritesPath = "OBR_AN.DFW";
56 const char *overlaysPath = "OBR_MAS.DFW";
57 const char *roomsPath = "MIST.DFW";
58 const char *animationsPath = "ANIM.DFW";
59 const char *iconsPath = "HRA.DFW";
60 const char *walkingMapsPath = "MAPY.DFW";
61 const char *itemsPath = "IKONY.DFW";
62 const char *itemImagesPath = "OBR_IK.DFW";
63 const char *initPath = "INIT.DFW";
64 const char *stringsPath = "RETEZCE.DFW";
65 const char *soundsPath = "CD2.SAM";
66 const char *dubbingPath = "CD.SAM";
67 const char *musicPathMask = "HUDBA%d.MID";
68 
69 const uint kSoundsFrequency = 13000;
70 const uint kDubbingFrequency = 22050;
71 
DraciEngine(OSystem * syst,const ADGameDescription * gameDesc)72 DraciEngine::DraciEngine(OSystem *syst, const ADGameDescription *gameDesc)
73  : Engine(syst), _rnd("draci") {
74 
75 	setDebugger(new DraciConsole(this));
76 
77 	_screen = 0;
78 	_mouse = 0;
79 	_game = 0;
80 	_script = 0;
81 	_anims = 0;
82 	_sound = 0;
83 	_music = 0;
84 	_smallFont = 0;
85 	_bigFont = 0;
86 	_iconsArchive = 0;
87 	_objectsArchive = 0;
88 	_spritesArchive = 0;
89 	_paletteArchive = 0;
90 	_roomsArchive = 0;
91 	_overlaysArchive = 0;
92 	_animationsArchive = 0;
93 	_walkingMapsArchive = 0;
94 	_itemsArchive = 0;
95 	_itemImagesArchive = 0;
96 	_initArchive = 0;
97 	_stringsArchive = 0;
98 	_soundsArchive = 0;
99 	_dubbingArchive = 0;
100 	_showWalkingMap = 0;
101 	_pauseStartTime = 0;
102 }
103 
hasFeature(EngineFeature f) const104 bool DraciEngine::hasFeature(EngineFeature f) const {
105 	return (f == kSupportsSubtitleOptions) ||
106 		(f == kSupportsReturnToLauncher) ||
107 		(f == kSupportsLoadingDuringRuntime) ||
108 		(f == kSupportsSavingDuringRuntime);
109 }
110 
openAnyPossibleDubbing()111 static SoundArchive* openAnyPossibleDubbing() {
112 	debugC(1, kDraciGeneralDebugLevel, "Trying to find original dubbing");
113 	LegacySoundArchive *legacy = new LegacySoundArchive(dubbingPath, kDubbingFrequency);
114 	if (legacy->isOpen() && legacy->size()) {
115 		debugC(1, kDraciGeneralDebugLevel, "Found original dubbing");
116 		return legacy;
117 	}
118 	delete legacy;
119 
120 	// The original uncompressed dubbing cannot be found.  Try to open the
121 	// newer compressed version.
122 	debugC(1, kDraciGeneralDebugLevel, "Trying to find compressed dubbing");
123 	ZipSoundArchive *zip = new ZipSoundArchive();
124 
125 	zip->openArchive("dub-raw.zzz", "buf", RAW80, kDubbingFrequency);
126 	if (zip->isOpen() && zip->size()) return zip;
127 #ifdef USE_FLAC
128 	zip->openArchive("dub-flac.zzz", "flac", FLAC);
129 	if (zip->isOpen() && zip->size()) return zip;
130 #endif
131 #ifdef USE_VORBIS
132 	zip->openArchive("dub-ogg.zzz", "ogg", OGG);
133 	if (zip->isOpen() && zip->size()) return zip;
134 #endif
135 #ifdef USE_MAD
136 	zip->openArchive("dub-mp3.zzz", "mp3", MP3);
137 	if (zip->isOpen() && zip->size()) return zip;
138 #endif
139 
140 	// Return an empty (but initialized) archive anyway.
141 	return zip;
142 }
143 
init()144 int DraciEngine::init() {
145 	// Initialize graphics using following:
146 	initGraphics(kScreenWidth, kScreenHeight);
147 
148 	// Open game's archives
149 	_initArchive = new BArchive(initPath);
150 	_objectsArchive = new BArchive(objectsPath);
151 	_spritesArchive = new BArchive(spritesPath);
152 	_paletteArchive = new BArchive(palettePath);
153 	_roomsArchive = new BArchive(roomsPath);
154 	_overlaysArchive = new BArchive(overlaysPath);
155 	_animationsArchive = new BArchive(animationsPath);
156 	_iconsArchive = new BArchive(iconsPath);
157 	_walkingMapsArchive = new BArchive(walkingMapsPath);
158 	_itemsArchive = new BArchive(itemsPath);
159 	_itemImagesArchive = new BArchive(itemImagesPath);
160 	_stringsArchive = new BArchive(stringsPath);
161 
162 	_soundsArchive = new LegacySoundArchive(soundsPath, kSoundsFrequency);
163 	_dubbingArchive = openAnyPossibleDubbing();
164 	_sound = new Sound(_mixer);
165 
166 	_music = new MusicPlayer(musicPathMask);
167 
168 	// Setup mixer
169 	syncSoundSettings();
170 
171 	// Load the game's fonts
172 	_smallFont = new Font(kFontSmall);
173 	_bigFont = new Font(kFontBig);
174 
175 	_screen = new Screen(this);
176 	_anims = new AnimationManager(this);
177 	_mouse = new Mouse(this);
178 	_script = new Script(this);
179 	_game = new Game(this);
180 
181 	if (!_objectsArchive->isOpen()) {
182 		debugC(2, kDraciGeneralDebugLevel, "ERROR - Opening objects archive failed");
183 		return Common::kUnknownError;
184 	}
185 
186 	if (!_spritesArchive->isOpen()) {
187 		debugC(2, kDraciGeneralDebugLevel, "ERROR - Opening sprites archive failed");
188 		return Common::kUnknownError;
189 	}
190 
191 	if (!_paletteArchive->isOpen()) {
192 		debugC(2, kDraciGeneralDebugLevel, "ERROR - Opening palette archive failed");
193 		return Common::kUnknownError;
194 	}
195 
196 	if (!_roomsArchive->isOpen()) {
197 		debugC(2, kDraciGeneralDebugLevel, "ERROR - Opening rooms archive failed");
198 		return Common::kUnknownError;
199 	}
200 
201 	if (!_overlaysArchive->isOpen()) {
202 		debugC(2, kDraciGeneralDebugLevel, "ERROR - Opening overlays archive failed");
203 		return Common::kUnknownError;
204 	}
205 
206 	if (!_animationsArchive->isOpen()) {
207 		debugC(2, kDraciGeneralDebugLevel, "ERROR - Opening animations archive failed");
208 		return Common::kUnknownError;
209 	}
210 
211 	if (!_iconsArchive->isOpen()) {
212 		debugC(2, kDraciGeneralDebugLevel, "ERROR - Opening icons archive failed");
213 		return Common::kUnknownError;
214 	}
215 
216 	if (!_walkingMapsArchive->isOpen()) {
217 		debugC(2, kDraciGeneralDebugLevel, "ERROR - Opening walking maps archive failed");
218 		return Common::kUnknownError;
219 	}
220 
221 	if (!_soundsArchive->isOpen()) {
222 		debugC(2, kDraciGeneralDebugLevel, "ERROR - Opening sounds archive failed");
223 		return Common::kUnknownError;
224 	}
225 
226 	if (!_dubbingArchive->isOpen()) {
227 		debugC(2, kDraciGeneralDebugLevel, "WARNING - Opening dubbing archive failed");
228 	}
229 
230 	_showWalkingMap = false;
231 
232 	// Basic archive test
233 	debugC(2, kDraciGeneralDebugLevel, "Running archive tests...");
234 	Common::String path("INIT.DFW");
235 	BArchive ar(path);
236 	const BAFile *f;
237 	debugC(3, kDraciGeneralDebugLevel, "Number of file streams in archive: %d", ar.size());
238 
239 	if (ar.isOpen()) {
240 		f = ar.getFile(0);
241 	} else {
242 		debugC(2, kDraciGeneralDebugLevel, "ERROR - Archive not opened");
243 		return Common::kUnknownError;
244 	}
245 
246 	debugC(3, kDraciGeneralDebugLevel, "First 10 bytes of file %d: ", 0);
247 	for (uint i = 0; i < 10; ++i) {
248 		debugC(3, kDraciGeneralDebugLevel, "0x%02x%c", f->_data[i], (i < 9) ? ' ' : '\n');
249 	}
250 
251 	return Common::kNoError;
252 }
253 
handleEvents()254 void DraciEngine::handleEvents() {
255 	Common::Event event;
256 
257 	while (_eventMan->pollEvent(event)) {
258 		switch (event.type) {
259 		case Common::EVENT_KEYDOWN:
260 			switch (event.kbd.keycode) {
261 			case Common::KEYCODE_RIGHT:
262 				if (gDebugLevel >= 0) {
263 					_game->scheduleEnteringRoomUsingGate(_game->nextRoomNum(), 0);
264 				}
265 				break;
266 			case Common::KEYCODE_LEFT:
267 				if (gDebugLevel >= 0) {
268 					_game->scheduleEnteringRoomUsingGate(_game->prevRoomNum(), 0);
269 				}
270 				break;
271 			case Common::KEYCODE_ESCAPE: {
272 				if (_game->getLoopStatus() == kStatusInventory &&
273 				   _game->getLoopSubstatus() == kOuterLoop) {
274 					_game->inventoryDone();
275 					break;
276 				}
277 
278 				const int escRoom = _game->getRoomNum() != _game->getMapRoom()
279 					? _game->getEscRoom() : _game->getPreviousRoomNum();
280 
281 				// Check if there is an escape room defined for the current room
282 				if (escRoom >= 0) {
283 
284 					// Schedule room change
285 					// TODO: gate 0 (always present) is not always best for
286 					// returning from the map, e.g. in the starting location.
287 					// also, after loading the game, we shouldn't run any gate
288 					// program, but rather restore the state of all objects.
289 					_game->scheduleEnteringRoomUsingGate(escRoom, 0);
290 
291 					// Immediately cancel any running animation or dubbing and
292 					// end any currently running GPL programs.  In the intro it
293 					// works as intended---skipping the rest of it.
294 					//
295 					// In the map, this causes that animation on newly
296 					// discovered locations will be re-run next time and
297 					// cut-scenes won't be played.
298 					_game->setExitLoop(true);
299 					_script->endCurrentProgram(true);
300 				}
301 				break;
302 			}
303 			case Common::KEYCODE_m:
304 				if (_game->getLoopStatus() == kStatusOrdinary) {
305 					const int new_room = _game->getRoomNum() != _game->getMapRoom()
306 						? _game->getMapRoom() : _game->getPreviousRoomNum();
307 					_game->scheduleEnteringRoomUsingGate(new_room, 0);
308 				}
309 				break;
310 			case Common::KEYCODE_w:
311 				// Show walking map toggle
312 				_showWalkingMap = !_showWalkingMap;
313 				_game->switchWalkingAnimations(_showWalkingMap);
314 				break;
315 			case Common::KEYCODE_q:
316 				_game->setWantQuickHero(!_game->getWantQuickHero());
317 				break;
318 			case Common::KEYCODE_i:
319 				if (_game->getRoomNum() == _game->getMapRoom() ||
320 				    _game->getLoopSubstatus() != kOuterLoop) {
321 					break;
322 				}
323 				if (_game->getLoopStatus() == kStatusInventory) {
324 					_game->inventoryDone();
325 				} else if (_game->getLoopStatus() == kStatusOrdinary) {
326 					_game->inventoryInit();
327 				}
328 				break;
329 			case Common::KEYCODE_F5:
330 				if (event.kbd.hasFlags(0)) {
331 					openMainMenuDialog();
332 				}
333 				break;
334 			case Common::KEYCODE_COMMA:
335 			case Common::KEYCODE_PERIOD:
336 			case Common::KEYCODE_SLASH:
337 				if ((_game->getLoopStatus() == kStatusOrdinary ||
338 				    _game->getLoopStatus() == kStatusInventory) &&
339 				   _game->getLoopSubstatus() == kOuterLoop &&
340 				   _game->getRoomNum() != _game->getMapRoom()) {
341 					_game->inventorySwitch(event.kbd.keycode);
342 				}
343 				break;
344 			default:
345 				break;
346 			}
347 			break;
348 		default:
349 			_mouse->handleEvent(event);
350 		}
351 	}
352 
353 	// Handle EVENT_QUIT and EVENT_RETURN_TO_LAUNCHER.
354 	if (shouldQuit()) {
355 		_game->setQuit(true);
356 		_script->endCurrentProgram(true);
357 	}
358 }
359 
~DraciEngine()360 DraciEngine::~DraciEngine() {
361 	// Dispose your resources here
362 
363 	// If the common library supported Boost's scoped_ptr<>, then wrapping
364 	// all the following pointers and many more would be appropriate.  So
365 	// far, there is only SharedPtr, which I feel being an overkill for
366 	// easy deallocation.
367 	// TODO: We have ScopedPtr nowadays. Maybe should adapt this code then?
368 	delete _smallFont;
369 	delete _bigFont;
370 
371 	delete _mouse;
372 	delete _script;
373 	delete _anims;
374 	delete _game;
375 	delete _screen;
376 
377 	delete _initArchive;
378 	delete _paletteArchive;
379 	delete _objectsArchive;
380 	delete _spritesArchive;
381 	delete _roomsArchive;
382 	delete _overlaysArchive;
383 	delete _animationsArchive;
384 	delete _iconsArchive;
385 	delete _walkingMapsArchive;
386 	delete _itemsArchive;
387 	delete _itemImagesArchive;
388 	delete _stringsArchive;
389 
390 	delete _sound;
391 	delete _music;
392 	delete _soundsArchive;
393 	delete _dubbingArchive;
394 }
395 
run()396 Common::Error DraciEngine::run() {
397 	init();
398 	setTotalPlayTime(0);
399 	_game->init();
400 
401 	// Load game from specified slot, if any
402 	if (ConfMan.hasKey("save_slot")) {
403 		loadGameState(ConfMan.getInt("save_slot"));
404 	}
405 
406 	_game->start();
407 	return Common::kNoError;
408 }
409 
pauseEngineIntern(bool pause)410 void DraciEngine::pauseEngineIntern(bool pause) {
411 	Engine::pauseEngineIntern(pause);
412 	if (pause) {
413 		_pauseStartTime = _system->getMillis();
414 
415 		_anims->pauseAnimations();
416 		_sound->pauseSound();
417 		_sound->pauseVoice();
418 		_music->pause();
419 	} else {
420 		_anims->unpauseAnimations();
421 		_sound->resumeSound();
422 		_sound->resumeVoice();
423 		_music->resume();
424 
425 		// Adjust engine start time
426 		const int delta = _system->getMillis() - _pauseStartTime;
427 		_game->shiftSpeechAndFadeTick(delta);
428 		_pauseStartTime = 0;
429 	}
430 }
431 
syncSoundSettings()432 void DraciEngine::syncSoundSettings() {
433 	Engine::syncSoundSettings();
434 
435 	_sound->setVolume();
436 	_music->syncVolume();
437 }
438 
getSavegameFile(int saveGameIdx)439 Common::String DraciEngine::getSavegameFile(int saveGameIdx) {
440 	return Common::String::format("draci.s%02d", saveGameIdx);
441 }
442 
loadGameState(int slot)443 Common::Error DraciEngine::loadGameState(int slot) {
444 	// When called from run() using save_slot, the next operation is the
445 	// call to start() calling enterNewRoom().
446 	// When called from handleEvents() in the middle of the game, the next
447 	// operation after handleEvents() exits from loop(), and returns to
448 	// start() to the same place as above.
449 	// In both cases, we are safe to override the data structures right
450 	// here are now, without waiting for any other code to finish, thanks
451 	// to our constraint in canLoadGameStateCurrently() and to having
452 	// enterNewRoom() called right after we exit from here.
453 	return loadSavegameData(slot, this);
454 }
455 
canLoadGameStateCurrently()456 bool DraciEngine::canLoadGameStateCurrently() {
457 	return (_game->getLoopStatus() == kStatusOrdinary) &&
458 		(_game->getLoopSubstatus() == kOuterLoop);
459 }
460 
saveGameState(int slot,const Common::String & desc,bool isAutosave)461 Common::Error DraciEngine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
462 	return saveSavegameData(slot, desc, *this);
463 }
464 
canSaveGameStateCurrently()465 bool DraciEngine::canSaveGameStateCurrently() {
466 	return (_game->getLoopStatus() == kStatusOrdinary) &&
467 		(_game->getLoopSubstatus() == kOuterLoop);
468 }
469 
470 } // End of namespace Draci
471