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