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