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 /*
24 * This code is based on original Mortville Manor DOS source code
25 * Copyright (c) 1987-1989 Lankhor
26 */
27
28 #include "mortevielle/mortevielle.h"
29
30 #include "mortevielle/dialogs.h"
31 #include "mortevielle/menu.h"
32 #include "mortevielle/mouse.h"
33 #include "mortevielle/outtext.h"
34 #include "mortevielle/saveload.h"
35 #include "mortevielle/outtext.h"
36
37 #include "common/system.h"
38 #include "common/config-manager.h"
39 #include "common/debug-channels.h"
40 #include "common/translation.h"
41 #include "engines/util.h"
42 #include "engines/engine.h"
43 #include "graphics/palette.h"
44 #include "graphics/pixelformat.h"
45
46 namespace Mortevielle {
47
48 MortevielleEngine *g_vm;
49
MortevielleEngine(OSystem * system,const MortevielleGameDescription * gameDesc)50 MortevielleEngine::MortevielleEngine(OSystem *system, const MortevielleGameDescription *gameDesc):
51 Engine(system), _gameDescription(gameDesc), _randomSource("mortevielle") {
52
53 g_vm = this;
54 setDebugger(new Debugger(this));
55 _dialogManager = new DialogManager(this);
56 _screenSurface = new ScreenSurface(this);
57 _mouse = new MouseHandler(this);
58 _text = new TextHandler(this);
59 _soundManager = new SoundManager(this, _mixer);
60 _savegameManager = new SavegameManager(this);
61 _menu = new Menu(this);
62
63 _lastGameFrame = 0;
64 _mouseClick = false;
65 _inMainGameLoop = false;
66 _quitGame = false;
67 _pauseStartTime = -1;
68
69 _roomPresenceLuc = false;
70 _roomPresenceIda = false;
71 _purpleRoomPresenceLeo = false;
72 _roomPresenceGuy = false;
73 _roomPresenceEva = false;
74 _roomPresenceMax = false;
75 _roomPresenceBob = false;
76 _roomPresencePat = false;
77 _toiletsPresenceBobMax = false;
78 _bathRoomPresenceBobMax = false;
79 _juliaRoomPresenceLeo = false;
80
81 _soundOff = false;
82 _largestClearScreen = false;
83 _hiddenHero = false;
84 _heroSearching = false;
85 _keyPressedEsc = false;
86 _reloadCFIEC = false;
87
88 _outsideOnlyFl = true;
89 _col = false;
90 _syn = false;
91 _obpart = false;
92 _destinationOk = false;
93 _anyone = false;
94 _uptodatePresence = false;
95
96 _textColor = 0;
97 _place = -1;
98
99 _x26KeyCount = -1;
100 _caff = -1;
101 _day = 0;
102
103 _curPict = nullptr;
104 _curAnim = nullptr;
105 _rightFramePict = nullptr;
106
107 resetCoreVar();
108
109 _maff = 0;
110 _crep = 0;
111
112 _minute = 0;
113 _curSearchObjId = 0;
114 _controlMenu = 0;
115 _startTime = 0;
116 _endTime = 0;
117 _roomDoorId = OWN_ROOM;
118 _openObjCount = 0;
119 _takeObjCount = 0;
120 _num = 0;
121 _searchCount = 0;
122 _introSpeechPlayed = false;
123 _inGameHourDuration = 0;
124 _x = 0;
125 _y = 0;
126 _currentHourCount = 0;
127 _currentTime = 0;
128 _cfiecBuffer = nullptr;
129 _cfiecBufferSize = 0;
130 for (int i = 0; i < 601; i++) {
131 _dialogHintArray[i]._hintId = 0;
132 _dialogHintArray[i]._point = 0;
133 }
134 _currMenu = OPCODE_NONE;
135 _currAction = OPCODE_NONE;
136 _menuOpcode = OPCODE_NONE;
137 _addFix = 0;
138 _currBitIndex = 0;
139 _currDay = 0;
140 _currHour = 10;
141 _currHalfHour = 0;
142 _hour = 10;
143 _key = 0;
144 _manorDistance = 0;
145 _numpal = 0;
146 _savedBitIndex = 0;
147 _endGame = false;
148 _loseGame = false;
149 _txxFileFl = false;
150 _is = 0;
151 }
152
~MortevielleEngine()153 MortevielleEngine::~MortevielleEngine() {
154 delete _menu;
155 delete _savegameManager;
156 delete _soundManager;
157 delete _text;
158 delete _mouse;
159 delete _screenSurface;
160 delete _dialogManager;
161
162 free(_curPict);
163 free(_curAnim);
164 free(_rightFramePict);
165 }
166
167 /**
168 * Specifies whether the engine supports given features
169 */
hasFeature(EngineFeature f) const170 bool MortevielleEngine::hasFeature(EngineFeature f) const {
171 return
172 (f == kSupportsReturnToLauncher) ||
173 (f == kSupportsLoadingDuringRuntime) ||
174 (f == kSupportsSavingDuringRuntime);
175 }
176
177 /**
178 * Return true if a game can currently be loaded
179 */
canLoadGameStateCurrently()180 bool MortevielleEngine::canLoadGameStateCurrently() {
181 // Saving is only allowed in the main game event loop
182 return _inMainGameLoop;
183 }
184
185 /**
186 * Return true if a game can currently be saved
187 */
canSaveGameStateCurrently()188 bool MortevielleEngine::canSaveGameStateCurrently() {
189 // Loading is only allowed in the main game event loop
190 return _inMainGameLoop;
191 }
192
193 /**
194 * Load in a savegame at the specified slot number
195 */
loadGameState(int slot)196 Common::Error MortevielleEngine::loadGameState(int slot) {
197 return _savegameManager->loadGame(slot);
198 }
199
200 /**
201 * Save the current game
202 */
saveGameState(int slot,const Common::String & desc,bool isAutosave)203 Common::Error MortevielleEngine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
204 if (slot == 0)
205 return Common::kWritingFailed;
206
207 return _savegameManager->saveGame(slot, desc);
208 }
209
210 /**
211 * Support method that generates a savegame name
212 * @param slot Slot number
213 */
generateSaveFilename(const Common::String & target,int slot)214 Common::String MortevielleEngine::generateSaveFilename(const Common::String &target, int slot) {
215 if (slot == 0)
216 // Initial game state loaded when the game starts
217 return "sav0.mor";
218
219 return Common::String::format("%s.%03d", target.c_str(), slot);
220 }
221
222 /**
223 * Pause the game.
224 */
pauseEngineIntern(bool pause)225 void MortevielleEngine::pauseEngineIntern(bool pause) {
226 Engine::pauseEngineIntern(pause);
227 if (pause) {
228 if (_pauseStartTime == -1)
229 _pauseStartTime = readclock();
230 } else {
231 if (_pauseStartTime != -1) {
232 int pauseEndTime = readclock();
233 _currentTime += (pauseEndTime - _pauseStartTime);
234 if (_uptodatePresence)
235 _startTime += (pauseEndTime - _pauseStartTime);
236 }
237 _pauseStartTime = -1;
238 }
239 }
240
241 /**
242 * Initialize the game state
243 */
initialize()244 Common::ErrorCode MortevielleEngine::initialize() {
245 // Initialize graphics mode
246 initGraphics(SCREEN_WIDTH, SCREEN_HEIGHT);
247
248 // Set up an intermediate screen surface
249 _screenSurface->create(SCREEN_WIDTH, SCREEN_HEIGHT, Graphics::PixelFormat::createFormatCLUT8());
250
251 _txxFileFl = false;
252 // Load texts from TXX files
253 loadTexts();
254
255 // Load the mort.dat resource
256 Common::ErrorCode result = loadMortDat();
257 if (result != Common::kNoError) {
258 _screenSurface->free();
259 return result;
260 }
261
262 // Load some error messages (was previously in chartex())
263 _hintPctMessage = getString(580); // You should have noticed %d hints
264
265 // Set default EGA palette
266 _paletteManager.setDefaultPalette();
267
268 // Setup the mouse cursor
269 initMouse();
270
271 loadPalette();
272 loadCFIPH();
273 loadCFIEC();
274 decodeNumber(&_cfiecBuffer[161 * 16], (_cfiecBufferSize - (161 * 16)) / 64);
275 _x26KeyCount = 1;
276 initMaxAnswer();
277 initMouse();
278
279 loadPlaces();
280 _soundOff = false;
281 _largestClearScreen = false;
282
283 testKeyboard();
284 showConfigScreen();
285 testKeyboard();
286 clearScreen();
287
288 _soundManager->loadNoise();
289 _soundManager->loadAmbiantSounds();
290
291 return Common::kNoError;
292 }
293
294 /**
295 * Loads the contents of the mort.dat data file
296 */
loadMortDat()297 Common::ErrorCode MortevielleEngine::loadMortDat() {
298 Common::File f;
299
300 // Open the mort.dat file
301 if (!f.open(MORT_DAT)) {
302 GUIErrorMessageFormat(_("Unable to locate the '%s' engine data file."), MORT_DAT);
303 return Common::kReadingFailed;
304 }
305
306 // Validate the data file header
307 char fileId[4];
308 f.read(fileId, 4);
309 if (strncmp(fileId, "MORT", 4) != 0) {
310 GUIErrorMessageFormat(_("The '%s' engine data file is corrupt."), MORT_DAT);
311 return Common::kReadingFailed;
312 }
313
314 // Check the version
315 int majVer = f.readByte();
316 int minVer = f.readByte();
317
318 if (majVer < MORT_DAT_REQUIRED_VERSION) {
319 GUIErrorMessageFormat(
320 _("Incorrect version of the '%s' engine data file found. Expected %d.%d but got %d.%d."),
321 MORT_DAT, MORT_DAT_REQUIRED_VERSION, 0, majVer, minVer);
322 return Common::kReadingFailed;
323 }
324
325 // Loop to load resources from the data file
326 while (f.pos() < f.size()) {
327 // Get the Id and size of the next resource
328 char dataType[4];
329 int dataSize;
330 f.read(dataType, 4);
331 dataSize = f.readUint16LE();
332
333 if (!strncmp(dataType, "FONT", 4)) {
334 // Font resource
335 _screenSurface->readFontData(f, dataSize);
336 } else if (!strncmp(dataType, "SSTR", 4)) {
337 readStaticStrings(f, dataSize, kStaticStrings);
338 } else if ((!strncmp(dataType, "GSTR", 4)) && (!_txxFileFl)) {
339 readStaticStrings(f, dataSize, kGameStrings);
340 } else if (!strncmp(dataType, "VERB", 4)) {
341 _menu->readVerbNums(f, dataSize);
342 } else {
343 // Unknown section
344 f.skip(dataSize);
345 }
346 }
347
348 // Close the file
349 f.close();
350
351 assert(_engineStrings.size() > 0);
352 return Common::kNoError;
353 }
354
355
356 /**
357 * Read in a static strings block, and if the language matches, load up the static strings
358 */
readStaticStrings(Common::File & f,int dataSize,DataType dataType)359 void MortevielleEngine::readStaticStrings(Common::File &f, int dataSize, DataType dataType) {
360 // Figure out what language Id is needed
361 byte desiredLanguageId;
362 switch(getLanguage()) {
363 case Common::EN_ANY:
364 desiredLanguageId = MORTDAT_LANG_ENGLISH;
365 break;
366 case Common::FR_FRA:
367 desiredLanguageId = MORTDAT_LANG_FRENCH;
368 break;
369 case Common::DE_DEU:
370 desiredLanguageId = MORTDAT_LANG_GERMAN;
371 break;
372 default:
373 warning("Language not supported, switching to English");
374 desiredLanguageId = MORTDAT_LANG_ENGLISH;
375 break;
376 }
377
378 // Read in the language
379 byte languageId = f.readByte();
380 --dataSize;
381
382 // If the language isn't correct, then skip the entire block
383 if (languageId != desiredLanguageId) {
384 f.skip(dataSize);
385 return;
386 }
387
388 // Load in each of the strings
389 while (dataSize > 0) {
390 Common::String s;
391 char ch;
392 while ((ch = (char)f.readByte()) != '\0')
393 s += ch;
394
395 if (dataType == kStaticStrings)
396 _engineStrings.push_back(s);
397 else if (dataType == kGameStrings)
398 _gameStrings.push_back(s);
399
400 dataSize -= s.size() + 1;
401 }
402 assert(dataSize == 0);
403 }
404
405 /*-------------------------------------------------------------------------*/
406
run()407 Common::Error MortevielleEngine::run() {
408 // Initialize the game
409 Common::ErrorCode err = initialize();
410 if (err != Common::kNoError)
411 return err;
412
413 // Check for a savegame
414 int loadSlot = 0;
415 if (ConfMan.hasKey("save_slot")) {
416 int gameToLoad = ConfMan.getInt("save_slot");
417 if ((gameToLoad >= 1) && (gameToLoad <= 999))
418 loadSlot = gameToLoad;
419 }
420
421 if (loadSlot == 0)
422 // Show the game introduction
423 showIntroduction();
424 else {
425 _caff = 51;
426 _text->taffich();
427 }
428
429 // Either load the initial game state savegame, or the specified savegame number
430 adzon();
431 resetVariables();
432 if (loadSlot != 0)
433 _savegameManager->loadSavegame(getSaveStateName(loadSlot));
434
435 // Run the main game loop
436 mainGame();
437
438 // Cleanup (allocated in initialize())
439 _screenSurface->free();
440 free(_soundManager->_cfiphBuffer);
441 free(_cfiecBuffer);
442
443 return Common::kNoError;
444 }
445
446 /**
447 * Show the game introduction
448 */
showIntroduction()449 void MortevielleEngine::showIntroduction() {
450 _dialogManager->displayIntroScreen(false);
451 _dialogManager->checkForF8(142, false);
452 if (shouldQuit())
453 return;
454
455 _dialogManager->displayIntroFrame2();
456 _dialogManager->checkForF8(143, true);
457 if (shouldQuit())
458 return;
459
460 showTitleScreen();
461 music();
462 _mixer->stopAll();
463 }
464
465 /**
466 * Main game loop. Handles potentially playing the game multiple times, such as if the player
467 * loses, and chooses to start playing the game again.
468 */
mainGame()469 void MortevielleEngine::mainGame() {
470 if (_reloadCFIEC)
471 loadCFIEC();
472
473 for (_crep = 1; _crep <= _x26KeyCount; ++_crep)
474 decodeNumber(&_cfiecBuffer[161 * 16], (_cfiecBufferSize - (161 * 16)) / 64);
475
476 _menu->initMenu();
477
478 charToHour();
479 initGame();
480 clearScreen();
481 drawRightFrame();
482 _mouse->showMouse();
483
484 // Loop to play the game
485 do {
486 playGame();
487 if (shouldQuit())
488 return;
489 } while (!_quitGame);
490 }
491
492 /**
493 * This method handles playing a loaded game
494 * @remarks Originally called tjouer
495 */
playGame()496 void MortevielleEngine::playGame() {
497 gameLoaded();
498
499 // Loop handling actions until the game has to be quit, or show the lose or end sequence
500 do {
501 handleAction();
502 if (shouldQuit())
503 return;
504 } while (!((_quitGame) || (_endGame) || (_loseGame)));
505
506 if (_endGame)
507 endGame();
508 else if (_loseGame)
509 askRestart();
510 }
511
512 } // End of namespace Mortevielle
513