diff options
Diffstat (limited to 'engines/mortevielle')
25 files changed, 11145 insertions, 0 deletions
diff --git a/engines/mortevielle/actions.cpp b/engines/mortevielle/actions.cpp new file mode 100644 index 0000000000..5738a8fd3a --- /dev/null +++ b/engines/mortevielle/actions.cpp @@ -0,0 +1,1640 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This code is based on original Mortville Manor DOS source code + * Copyright (c) 1987-1989 Lankhor + */ + +#include "mortevielle/mortevielle.h" +#include "mortevielle/dialogs.h" +#include "mortevielle/menu.h" +#include "mortevielle/mouse.h" +#include "mortevielle/outtext.h" +#include "mortevielle/speech.h" + +#include "common/scummsys.h" + +namespace Mortevielle { + +/** + * Engine function - Move + * @remarks Originally called 'taller' + */ +void MortevielleEngine::fctMove() { + int oldMenu = (_menu._moveMenu[6]._menuId << 8) | _menu._moveMenu[6]._actionId; + if ((_coreVar._currPlace == ROOM26) && (_currAction == oldMenu)) { + _coreVar._currPlace = LANDING; + _caff = _coreVar._currPlace; + drawPictureWithText(); + handleDescriptionText(2, _coreVar._currPlace); + } + if ((_coreVar._currPlace == LANDING) && (_currAction == oldMenu)) { + if (!_syn) + displayTextInVerbBar(getEngineString(S_GO_TO)); + displayStatusArrow(); + + if (_keyPressedEsc) + _destinationOk = false; + + if ((_anyone) || (_keyPressedEsc)) + return; + + setCoordinates(1); + + if (_num == 0) + return; + + if (_num == 1) { + _coreVar._currPlace = OWN_ROOM; + _menu.setDestinationText(OWN_ROOM); + } else if (_num == 7) { + _coreVar._currPlace = ATTIC; + _menu.setDestinationText(ATTIC); + } else if (_num != 6) + _coreVar._currPlace = ROOM26; + + if ((_num > 1) && (_num < 6)) + _roomDoorId = _num - 1; + else if (_num > 7) + _roomDoorId = _num - 3; + + if (_num != 6) + prepareDisplayText(); + else + showMoveMenuAlert(); + return; + } + exitRoom(); + int menuChoice = 1; + oldMenu = (_menu._moveMenu[menuChoice]._menuId << 8) | _menu._moveMenu[menuChoice]._actionId; + while (oldMenu != _currAction) { + ++menuChoice; + oldMenu = (_menu._moveMenu[menuChoice]._menuId << 8) | _menu._moveMenu[menuChoice]._actionId; + } + + if (_coreVar._currPlace == MOUNTAIN) { + if (menuChoice == 1) + gotoManorFront(); + else if (menuChoice == 2) + checkManorDistance(); + _menu.setDestinationText(_coreVar._currPlace); + return; + } else if (_coreVar._currPlace == INSIDE_WELL) { + if (menuChoice == 1) + floodedInWell(); + else if (menuChoice == 2) + gotoManorBack(); + _menu.setDestinationText(_coreVar._currPlace); + return; + } else if ((_coreVar._currPlace == BUREAU) && (menuChoice == 1)) + menuChoice = 6; + else if (_coreVar._currPlace == KITCHEN) { + if (menuChoice == 2) + menuChoice = 6; + else if (menuChoice == 5) + menuChoice = 16; + } else if ((_coreVar._currPlace == CELLAR) && (menuChoice == 3)) + menuChoice = 6; + else if (((_coreVar._currPlace == LANDING) || (_coreVar._currPlace == ROOM26)) && (menuChoice == 4)) + menuChoice = 6; + + if ((_coreVar._currPlace > MOUNTAIN) && (_coreVar._currPlace != ROOM26)) + menuChoice += 10; + + if ((_coreVar._currPlace == CHAPEL) && (menuChoice == 13)) + menuChoice = 16; + else if (_coreVar._currPlace == MANOR_FRONT) { + if (menuChoice == 12) + menuChoice = 16; + else if (menuChoice > 13) + menuChoice = 15; + } else if ((_coreVar._currPlace == MANOR_BACK) && (menuChoice > 14)) + menuChoice = 15; + else if ((_coreVar._currPlace == WELL) && (menuChoice > 13) && (menuChoice != 17)) + menuChoice = 15; + + if (menuChoice == 1) + _coreVar._currPlace = BUREAU; + else if (menuChoice == 2) + _coreVar._currPlace = KITCHEN; + else if (menuChoice == 3) + _coreVar._currPlace = CELLAR; + else if (menuChoice == 4) + _coreVar._currPlace = LANDING; + else if (menuChoice == 5) + menuChoice = 12; + else if (menuChoice == 6) + menuChoice = 11; + + if (menuChoice == 11) + gotoDiningRoom(); + else if (menuChoice == 12) + gotoManorFront(); + else if (menuChoice == 13) + _coreVar._currPlace = CHAPEL; + else if (menuChoice == 14) + _coreVar._currPlace = WELL; + else if (menuChoice == 15) + checkManorDistance(); + else if (menuChoice == 16) + gotoManorBack(); + else if (menuChoice == 17) { + if ((_coreVar._wellObjectId != 120) && (_coreVar._wellObjectId != 140)) + _crep = 997; + else if (_coreVar._wellObjectId == 120) + _crep = 181; + else if (_coreVar._faithScore > 80) { + _crep = 1505; + loseGame(); + } else { + _coreVar._currPlace = INSIDE_WELL; + prepareDisplayText(); + } + } + if ((menuChoice < 5) || (menuChoice == 13) || (menuChoice == 14)) + prepareDisplayText(); + resetRoomVariables(_coreVar._currPlace); + _menu.setDestinationText(_coreVar._currPlace); +} + +/** + * Engine function - Take + * @remarks Originally called 'tprendre' + */ +void MortevielleEngine::fctTake() { + if (_caff > 99) { + int cx = _caff; + putInHand(cx); + if (_crep != 139) { + if (_currBitIndex > 0) + _coreVar._faithScore += 3; + if (_obpart) { + if (_coreVar._currPlace == PURPLE_ROOM) + _coreVar._purpleRoomObjectId = 0; + if (_coreVar._currPlace == ATTIC) { + if (_coreVar._atticBallHoleObjectId == _caff) + _coreVar._atticBallHoleObjectId = 0; + if (_coreVar._atticRodHoleObjectId == _caff) + _coreVar._atticRodHoleObjectId = 0; + } + if (_coreVar._currPlace == CELLAR) + _coreVar._cellarObjectId = 0; + if (_coreVar._currPlace == CRYPT) + _coreVar._cryptObjectId = 0; + if (_coreVar._currPlace == SECRET_PASSAGE) + _coreVar._secretPassageObjectId = 0; + if (_coreVar._currPlace == WELL) + _coreVar._wellObjectId = 0; + _menu.unsetSearchMenu(); + _obpart = false; + prepareDisplayText(); + } else { + _tabdon[kAcha + ((_mchai - 1) * 10) + _searchCount - 1] = 0; + tsuiv(); + ++_takeObjCount; + if (_takeObjCount > 6) { + _coreVar._faithScore += 2; + _takeObjCount = 0; + } + } + } + return; + } + if (!_syn) + displayTextInVerbBar(getEngineString(S_TAKE)); + displayStatusArrow(); + if ((_anyone) || (_keyPressedEsc)) + return; + if (_caff == 3) { + setCoordinates(2); + if (_num == 1) { + _crep = 152; + return; + } + } + setCoordinates(5); + if ((_num == 0) || ((_num == 1) && (_coreVar._currPlace == CRYPT))) { + setCoordinates(8); + if (_num != 0) { + if (_currBitIndex > 0) + _coreVar._faithScore += 3; + _crep = 997; + if ((_coreVar._currPlace == PURPLE_ROOM) && (_coreVar._purpleRoomObjectId != 0)) + putInHand(_coreVar._purpleRoomObjectId); + if ((_coreVar._currPlace == ATTIC) && (_num == 1) && (_coreVar._atticBallHoleObjectId != 0)) { + putInHand(_coreVar._atticBallHoleObjectId); + if ((_crep != 997) && (_crep != 139)) + displayAnimFrame(2, 7); + } + if ((_coreVar._currPlace == ATTIC) && (_num == 2) && (_coreVar._atticRodHoleObjectId != 0)) { + putInHand(_coreVar._atticRodHoleObjectId); + if ((_crep != 997) && (_crep != 139)) + displayAnimFrame(2, 6); + } + if ((_coreVar._currPlace == CELLAR) && (_coreVar._cellarObjectId != 0)) { + putInHand(_coreVar._cellarObjectId); + if ((_crep != 997) && (_crep != 139)) + displayAnimFrame(2, 2); + } + if ((_coreVar._currPlace == CRYPT) && (_coreVar._cryptObjectId != 0)) + putInHand(_coreVar._cryptObjectId); + + if ((_coreVar._currPlace == SECRET_PASSAGE) && (_coreVar._secretPassageObjectId != 0)) { + putInHand(_coreVar._secretPassageObjectId); + if ((_crep != 997) && (_crep != 139)) { + _crep = 182; + displayAnimFrame(2, 1); + } + } + if ((_coreVar._currPlace == WELL) && (_coreVar._wellObjectId != 0)) { + putInHand(_coreVar._wellObjectId); + if ((_crep != 997) && (_crep != 139)) + displayAnimFrame(2, 1); + } + if ((_crep != 997) && (_crep != 182) && (_crep != 139)) + _crep = 999; + } + } else { + if ( ((_coreVar._currPlace == OWN_ROOM) && (_num == 3)) + || ((_coreVar._currPlace == GREEN_ROOM) && (_num == 4)) + || ((_coreVar._currPlace == PURPLE_ROOM) && (_num == 1)) + || ((_coreVar._currPlace == DARKBLUE_ROOM) && (_num == 3)) + || ((_coreVar._currPlace == BLUE_ROOM) && (_num == 6)) + || ((_coreVar._currPlace == RED_ROOM) && (_num == 2)) + || ((_coreVar._currPlace == BATHROOM) && (_num == 6)) + || ((_coreVar._currPlace == GREEN_ROOM2) && (_num == 4)) + || ((_coreVar._currPlace == ROOM9) && (_num == 4)) + || ((_coreVar._currPlace == DINING_ROOM) && (_num > 2)) + || ((_coreVar._currPlace == BUREAU) && (_num == 7)) + || ((_coreVar._currPlace == KITCHEN) && (_num == 6)) + || ((_coreVar._currPlace == ATTIC) && (_num > 4)) + || ((_coreVar._currPlace > ATTIC) && (_coreVar._currPlace != INSIDE_WELL)) ) + _crep = 997; + else if (_coreVar._currPlace == INSIDE_WELL) { + _crep = 1504; + loseGame(); + } else + _crep = 120; + } +} +/** + * Engine function - Inventory / Take + * @remarks Originally called 'tsprendre' + */ +void MortevielleEngine::fctInventoryTake() { + int inventIndex = 0; + int oldMenu = 0; + do { + ++inventIndex; + oldMenu = (_menu._inventoryMenu[inventIndex]._menuId << 8) | _menu._inventoryMenu[inventIndex]._actionId; + } while (oldMenu != _currAction); + int cz = 0; + int cy = 0; + do { + ++cy; + if (_coreVar._inventory[cy] != 0) + ++cz; + } while (cz != inventIndex); + cz = _coreVar._inventory[cy]; + _coreVar._inventory[cy] = 0; + _menu.setInventoryText(); + putInHand(cz); + _crep = 998; + clearDescriptionBar(); +} + +/** + * Engine function - Lift + * @remarks Originally called 'tsoulever' + */ +void MortevielleEngine::fctLift() { + if (!_syn) + displayTextInVerbBar(getEngineString(S_LIFT)); + displayStatusArrow(); + if ((_anyone) || (_keyPressedEsc)) + return; + setCoordinates(3); + if (_num == 0) { + setCoordinates(8); + if (_num != 0) { + if (_currBitIndex > 0) + ++_coreVar._faithScore; + _crep = 997; + if ((_coreVar._currPlace == PURPLE_ROOM) && (_coreVar._purpleRoomObjectId != 0)) + treg(_coreVar._purpleRoomObjectId); + } + return; + } + if (_currBitIndex > 0) + ++_coreVar._faithScore; + int tmpPlace = _coreVar._currPlace; + if (_coreVar._currPlace == CRYPT) + tmpPlace = 14; + else if (_coreVar._currPlace == MOUNTAIN) + tmpPlace = 15; + _crep = _tabdon[kAsoul + (tmpPlace << 3) + (_num - 1)]; + if (_crep == 255) + _crep = 997; +} + +/** + * Engine function - Read + * @remarks Originally called 'tlire' + */ +void MortevielleEngine::fctRead() { + if (_caff > 99) + getReadDescription(_caff); + else { + if (!_syn) + displayTextInVerbBar(getEngineString(S_READ)); + displayStatusArrow(); + if (!(_anyone) && !(_keyPressedEsc)) { + setCoordinates(4); + if (_num != 0) + _crep = 107; + } + } +} + +/** + * Engine function - Self / Read + * @remarks Originally called 'tslire' + */ +void MortevielleEngine::fctSelfRead() { + if (_coreVar._selectedObjectId == 0) + _crep = 186; + else + getReadDescription(_coreVar._selectedObjectId); +} + +/** + * Engine function - Look + * @remarks Originally called 'tregarder' + */ +void MortevielleEngine::fctLook() { + if (_caff > 99) { + _crep = 103; + return; + } + if (!_syn) + displayTextInVerbBar(getEngineString(S_LOOK)); + displayStatusArrow(); + if ((_anyone) || (_keyPressedEsc)) + return; + setCoordinates(5); + if (_num == 0) { + setCoordinates(8); + _crep = 131; + if (_num != 0) { + if (_coreVar._currPlace == ATTIC) { + if (_num == 1) { + _crep = 164; + if (_coreVar._atticRodHoleObjectId != 0) + treg(_coreVar._atticRodHoleObjectId); + else if (_coreVar._atticBallHoleObjectId != 0) + treg(_coreVar._atticBallHoleObjectId); + } else { + _crep = 193; + if (_coreVar._atticRodHoleObjectId != 0) + treg(_coreVar._atticRodHoleObjectId); + } + } + if (_coreVar._currPlace == CELLAR) { + _crep = 164; + if (_coreVar._cellarObjectId != 0) + treg(_coreVar._cellarObjectId); + } + if (_coreVar._currPlace == SECRET_PASSAGE) { + _crep = 174; + if (_coreVar._secretPassageObjectId != 0) + treg(_coreVar._secretPassageObjectId); + } + if (_coreVar._currPlace == WELL) { + _crep = 131; + if (_coreVar._wellObjectId != 0) + treg(_coreVar._wellObjectId); + } + } + return; + } + int cx = _coreVar._currPlace; + if (_coreVar._currPlace == CHAPEL) + cx = 17; + if ((_coreVar._currPlace > MANOR_FRONT) && (_coreVar._currPlace < DOOR)) + cx -= 4; + if (_coreVar._currPlace == ROOM26) + cx = 21; + _crep = _tabdon[kArega + (cx * 7) + _num - 1]; + if ((_coreVar._currPlace == ATTIC) && (_num == 8)) + _crep = 126; + if (_coreVar._currPlace == MOUNTAIN) + _crep = 103; + if (_crep == 255) + _crep = 131; + if ((_coreVar._currPlace == GREEN_ROOM) && (_num == 1)) + treg(144); + if ((_coreVar._currPlace == BLUE_ROOM) && (_num == 3)) + treg(147); + if ((_coreVar._currPlace == GREEN_ROOM2) && (_num == 3)) + treg(149); + if ((_coreVar._currPlace == ROOM9) && (_num == 2)) + treg(30); + if ((_coreVar._currPlace == DINING_ROOM) && (_num == 3)) + treg(31); +} + +/** + * Engine function - Self / Look + * @remarks Originally called 'tsregarder' + */ +void MortevielleEngine::fctSelftLook() { + if (_coreVar._selectedObjectId != 0) + treg(_coreVar._selectedObjectId); + else + _crep = 186; +} + +/** + * Engine function - Search + * @remarks Originally called 'tfouiller' + */ +void MortevielleEngine::fctSearch() { + static const byte answerArr[14] = {123, 104, 123, 131, 131, 123, 104, 131, 123, 123, 106, 123, 123, 107}; + + if (_caff > 99) { + getSearchDescription(_caff); + return; + } + + if (!_syn) + displayTextInVerbBar(getEngineString(S_SEARCH)); + + displayStatusArrow(); + if (_anyone || _keyPressedEsc) + return; + + if (_coreVar._currPlace == INSIDE_WELL) { + _crep = 1504; + loseGame(); + return; + } + + setCoordinates(6); + if (_num == 0) { + setCoordinates(7); + if (_num != 0) { + int cx = 0; + do { + ++cx; + } while ((cx <= 6) && (_num != _openObjects[cx])); + if (_num != _openObjects[cx]) + _crep = 187; + else { + if (_currBitIndex > 0) + _coreVar._faithScore += 3; + + _mchai = rechai(); + if (_mchai != 0) { + _searchCount = 0; + _heroSearching = true; + _menu.setSearchMenu(); + tsuiv(); + } else + _crep = 997; + } + } else { + setCoordinates(8); + _crep = 997; + if (_num != 0) { + if (_currBitIndex > 0) + _coreVar._faithScore += 3; + if ((_coreVar._currPlace != WELL) && (_coreVar._currPlace != SECRET_PASSAGE) && (_coreVar._currPlace != ATTIC)) { + if (_coreVar._currPlace == PURPLE_ROOM) { + _crep = 123; + if (_coreVar._purpleRoomObjectId != 0) + treg(_coreVar._purpleRoomObjectId); + } + if (_coreVar._currPlace == CRYPT) { + _crep = 123; + if (_coreVar._cryptObjectId != 0) + treg(_coreVar._cryptObjectId); + } + } + } + } + } else { + if (_currBitIndex > 0) + _coreVar._faithScore += 3; + _crep = 997; + if (_coreVar._currPlace < CELLAR) + _crep = answerArr[_coreVar._currPlace]; + + if ((_coreVar._currPlace == TOILETS) && (_num == 2)) + _crep = 162; + + if (_coreVar._currPlace == KITCHEN) { + if ((_num == 3) || (_num == 4)) + _crep = 162; + else if (_num == 5) + _crep = 159; + } + + if (_coreVar._currPlace == MOUNTAIN) + _crep = 104; + else if (_coreVar._currPlace == CRYPT) + _crep = 155; + } +} + +/** + * Engine function - Self / Search + * @remarks Originally called 'tsfouiller' + */ +void MortevielleEngine::fctSelfSearch() { + if (_coreVar._selectedObjectId != 0) + getSearchDescription(_coreVar._selectedObjectId); + else + _crep = 186; +} + +/** + * Engine function - Open + * @remarks Originally called 'touvrir' + */ +void MortevielleEngine::fctOpen() { + if (!_syn) + displayTextInVerbBar(getEngineString(S_OPEN)); + + if (_caff == ROOM26) { + if (_roomDoorId != OWN_ROOM) { + _currAction = OPCODE_ENTER; + _syn = true; + } else + _crep = 997; + return; + } + + if (_caff == LANDING) { + showMoveMenuAlert(); + return; + } + + displayStatusArrow(); + if ((_anyone) || (_keyPressedEsc)) + return; + + setCoordinates(7); + if (_num != 0) { + if (_currBitIndex > 0) + _coreVar._faithScore += 2; + ++_openObjCount; + int tmpPlace = 0; + do { + ++tmpPlace; + } while (!((tmpPlace > 6) || (_openObjects[tmpPlace] == 0) || (_openObjects[tmpPlace] == _num))); + if (_openObjects[tmpPlace] != _num) { + if (!( ((_num == 3) && ((_coreVar._currPlace == OWN_ROOM) + || (_coreVar._currPlace == ROOM9) + || (_coreVar._currPlace == BLUE_ROOM) + || (_coreVar._currPlace == BATHROOM))) + || ((_num == 4) && ((_coreVar._currPlace == GREEN_ROOM) + || (_coreVar._currPlace == PURPLE_ROOM) + || (_coreVar._currPlace == RED_ROOM))) + || ((_coreVar._currPlace == DARKBLUE_ROOM) && (_num == 5)) + || ((_num == 6) && ((_coreVar._currPlace == BATHROOM) + || (_coreVar._currPlace == DINING_ROOM) + || (_coreVar._currPlace == GREEN_ROOM2) + || (_coreVar._currPlace == ATTIC))) + || ((_coreVar._currPlace == GREEN_ROOM2) && (_num == 2)) + || ((_coreVar._currPlace == KITCHEN) && (_num == 7))) ) { + if ( ((_coreVar._currPlace > DINING_ROOM) && (_coreVar._currPlace < CELLAR)) + || ((_coreVar._currPlace > RED_ROOM) && (_coreVar._currPlace < DINING_ROOM)) + || (_coreVar._currPlace == OWN_ROOM) + || (_coreVar._currPlace == PURPLE_ROOM) + || (_coreVar._currPlace == BLUE_ROOM)) { + if (getRandomNumber(1, 4) == 3) + _speechManager.startSpeech(7, 9, 1); + } + _openObjects[tmpPlace] = _num; + displayAnimFrame(1, _num); + } + tmpPlace = _coreVar._currPlace; + if (_coreVar._currPlace == CRYPT) + tmpPlace = CELLAR; + _crep = _tabdon[kAouvr + (tmpPlace * 7) + _num - 1]; + if (_crep == 254) + _crep = 999; + } else + // display "Already Opened" + _crep = 18; + } +} + +/** + * Engine function - Place + * @remarks Originally called 'tmettre' + */ +void MortevielleEngine::fctPlace() { + if (_coreVar._selectedObjectId == 0) { + _crep = 186; + return; + } + + if (!_syn) + displayTextInVerbBar(getEngineString(S_PUT)); + + displayStatusArrow(); + if (_keyPressedEsc) + _crep = 998; + + if ((_anyone) || (_keyPressedEsc)) + return; + + setCoordinates(8); + if (_num != 0) { + _crep = 999; + if (_caff == ATTIC) { + if (_num == 1) { + if (_coreVar._atticBallHoleObjectId != 0) { + _crep = 188; + } else { + _coreVar._atticBallHoleObjectId = _coreVar._selectedObjectId; + if (_coreVar._selectedObjectId == 141) + displayAnimFrame(1, 7); + } + } else if (_coreVar._atticRodHoleObjectId != 0) { + _crep = 188; + } else { + _coreVar._atticRodHoleObjectId = _coreVar._selectedObjectId; + if (_coreVar._selectedObjectId == 159) + displayAnimFrame(1, 6); + } + } + + if (_caff == CELLAR) { + if (_coreVar._cellarObjectId != 0) { + _crep = 188; + } else { + _coreVar._cellarObjectId = _coreVar._selectedObjectId; + if (_coreVar._selectedObjectId == 151) { + // Open hidden passage + displayAnimFrame(1, 2); + displayAnimFrame(1, 1); + handleDescriptionText(2, 165); + displayEmptyHand(); + _speechManager.startSpeech(6, -9, 1); + + // Do you want to enter the hidden passage? + int answer = _dialogManager.show(getEngineString(S_YES_NO), 1); + if (answer == 1) { + Common::String alertTxt = getString(582); + _dialogManager.show(alertTxt, 1); + + bool enterPassageFl = _dialogManager.showKnowledgeCheck(); + _mouse.hideMouse(); + hirs(); + drawRightFrame(); + clearDescriptionBar(); + clearVerbBar(); + _mouse.showMouse(); + prepareRoom(); + drawClock(); + if (_currBitIndex != 0) + showPeoplePresent(_currBitIndex); + else + displayAloneText(); + + _menu.displayMenu(); + if (enterPassageFl) { + _coreVar._currPlace = SECRET_PASSAGE; + _menu.setDestinationText(SECRET_PASSAGE); + } else { + _menu.setDestinationText(_coreVar._currPlace); + setPal(14); + drawPicture(); + displayAnimFrame(1, 2); + displayAnimFrame(1, 1); + alertTxt = getString(577); + _dialogManager.show(alertTxt, 1); + displayAnimFrame(2, 1); + _crep = 166; + } + prepareDisplayText(); + } else { + displayAnimFrame(2, 1); + _crep = 166; + } + return; + } + } + } + + if (_caff == CRYPT) { + if (_coreVar._cryptObjectId == 0) + _coreVar._cryptObjectId = _coreVar._selectedObjectId; + else + _crep = 188; + } + + if (_caff == SECRET_PASSAGE) { + if (_coreVar._secretPassageObjectId != 0) { + _crep = 188; + } else if (_coreVar._selectedObjectId == 143) { + _coreVar._secretPassageObjectId = 143; + displayAnimFrame(1, 1); + } else { + _crep = 1512; + loseGame(); + } + } + + if (_caff == WELL) { + if (_coreVar._wellObjectId != 0) { + _crep = 188; + } else if ((_coreVar._selectedObjectId == 140) || (_coreVar._selectedObjectId == 120)) { + _coreVar._wellObjectId = _coreVar._selectedObjectId; + displayAnimFrame(1, 1); + } else { + _crep = 185; + } + } + + if (_crep != 188) + displayEmptyHand(); + } +} + +/** + * Engine function - Turn + * @remarks Originally called 'ttourner' + */ +void MortevielleEngine::fctTurn() { + if (_caff > 99) { + _crep = 149; + return; + } + if (!_syn) + displayTextInVerbBar(getEngineString(S_TURN)); + displayStatusArrow(); + if ((_anyone) || (_keyPressedEsc)) + return; + setCoordinates(9); + if (_num != 0) { + _crep = 997; + if ((_coreVar._currPlace == ATTIC) && (_coreVar._atticRodHoleObjectId == 159) && (_coreVar._atticBallHoleObjectId == 141)) { + handleDescriptionText(2, 167); + _speechManager.startSpeech(7, 9, 1); + int answer = _dialogManager.show(getEngineString(S_YES_NO), 1); + if (answer == 1) + _endGame = true; + else + _crep = 168; + } + if ((_coreVar._currPlace == SECRET_PASSAGE) && (_coreVar._secretPassageObjectId == 143)) { + handleDescriptionText(2, 175); + clearVerbBar(); + _speechManager.startSpeech(6, -9, 1); + int answer = _dialogManager.show(getEngineString(S_YES_NO), 1); + if (answer == 1) { + _coreVar._currPlace = CRYPT; + prepareDisplayText(); + } else + _crep = 176; + } + } +} + +/** + * Engine function - Hide Self + * @remarks Originally called 'tcacher' + */ +void MortevielleEngine::fctSelfHide() { + if (!_syn) + displayTextInVerbBar(getEngineString(S_HIDE_SELF)); + displayStatusArrow(); + if (!(_anyone) && !(_keyPressedEsc)) { + setCoordinates(10); + if (_num == 0) + _hiddenHero = false; + else { + _hiddenHero = true; + _crep = 999; + } + } +} + +/** + * Engine function - Attach + * @remarks Originally called 'tattacher' + */ +void MortevielleEngine::fctAttach() { + if (_coreVar._selectedObjectId == 0) + _crep = 186; + else { + if (!_syn) + displayTextInVerbBar(getEngineString(S_TIE)); + displayStatusArrow(); + if (!(_anyone) && !(_keyPressedEsc)) { + setCoordinates(8); + _crep = 997; + if ((_num != 0) && (_coreVar._currPlace == WELL)) { + _crep = 999; + if ((_coreVar._selectedObjectId == 120) || (_coreVar._selectedObjectId == 140)) { + _coreVar._wellObjectId = _coreVar._selectedObjectId; + displayAnimFrame(1, 1); + } else + _crep = 185; + displayEmptyHand(); + } + } + } +} + +/** + * Engine function - Close + * @remarks Originally called 'tfermer' + */ +void MortevielleEngine::fctClose() { + if (!_syn) + displayTextInVerbBar(getEngineString(S_CLOSE)); + + if (_caff < ROOM26) { + displayStatusArrow(); + if (_keyPressedEsc) + _crep = 998; + if ((_anyone) || (_keyPressedEsc)) + return; + setCoordinates(7); + if (_num != 0) { + int cx = 0; + do { + ++cx; + } while ((cx <= 6) && (_num != _openObjects[cx])); + if (_num == _openObjects[cx]) { + displayAnimFrame(2, _num); + _crep = 998; + _openObjects[cx] = 0; + --_openObjCount; + if (_openObjCount < 0) + _openObjCount = 0; + int chai = rechai(); + if (_mchai == chai) + _mchai = 0; + } else { + _crep = 187; + } + } + } + if (_caff == ROOM26) + _crep = 999; +} + +/** + * Engine function - Knock + * @remarks Originally called 'tfrapper' + */ +void MortevielleEngine::fctKnock() { + if (!_syn) + displayTextInVerbBar(getEngineString(S_HIT)); + + if (_coreVar._currPlace == LANDING) { + _dialogManager.show(getEngineString(S_BEFORE_USE_DEP_MENU), 1); + return; + } + + if (_coreVar._currPlace < DOOR) { + displayStatusArrow(); + if (!(_anyone) && !(_keyPressedEsc)) { + if ((_coreVar._currPlace < MOUNTAIN) && (_coreVar._currPlace != LANDING)) + _crep = 133; + else + _crep = 997; + } + + return; + } + + if (_coreVar._currPlace == ROOM26) { + int rand = (getRandomNumber(0, 8)) - 4; + _speechManager.startSpeech(11, rand, 1); + int p = getPresenceStats(rand, _coreVar._faithScore, _roomDoorId); + int l = _roomDoorId; + if (l != OWN_ROOM) { + if (p != -500) { + if (rand > p) + _crep = 190; + else { + setPresenceFlags(l); + getKnockAnswer(); + } + } else + getKnockAnswer(); + } + + if (_roomDoorId == GREEN_ROOM2) + _crep = 190; + } +} + +/** + * Engine function - Self / Put + * @remarks Originally called 'tposer' + */ +void MortevielleEngine::fctSelfPut() { + if (!_syn) + displayTextInVerbBar(getEngineString(S_POSE)); + if (_coreVar._selectedObjectId == 0) + _crep = 186; + else { + if (_caff > 99) { + _crep = 999; + ajchai(); + if (_crep != 192) + displayEmptyHand(); + return; + } + displayStatusArrow(); + if ((_anyone) || (_keyPressedEsc)) + return; + setCoordinates(7); + _crep = 124; + if (_num != 0) { + int chai = rechai(); + if (chai == 0) + _crep = 997; + else { + int cx = 0; + do { + ++cx; + } while ((cx <= 6) && (_num != _openObjects[cx])); + if (_num != _openObjects[cx]) + _crep = 187; + else { + _mchai = chai; + _crep = 999; + } + } + } else { + setCoordinates(8); + if (_num != 0) { + _crep = 998; + if (_caff == PURPLE_ROOM) { + if (_coreVar._purpleRoomObjectId != 0) + _crep = 188; + else + _coreVar._purpleRoomObjectId = _coreVar._selectedObjectId; + } + + if (_caff == ATTIC) { + if (_num == 1) { + if (_coreVar._atticBallHoleObjectId != 0) + _crep = 188; + else + _coreVar._atticBallHoleObjectId = _coreVar._selectedObjectId; + } else if (_coreVar._atticRodHoleObjectId != 0) { + _crep = 188; + } else { + _coreVar._atticRodHoleObjectId = _coreVar._selectedObjectId; + } + } + + if (_caff == CRYPT) { + if (_coreVar._cryptObjectId != 0) + _crep = 188; + else + _coreVar._cryptObjectId = _coreVar._selectedObjectId; + } + + if (_caff == WELL) + _crep = 185; + if ((_caff == CELLAR) || (_caff == SECRET_PASSAGE)) + _crep = 124; + } else { + _crep = 124; + if (_caff == WELL) { + setCoordinates(5); + if (_num != 0) + _crep = 185; + } + } + } + if (_caff == INSIDE_WELL) + _crep = 185; + if ((_crep == 999) || (_crep == 185) || (_crep == 998)) { + if (_crep == 999) + ajchai(); + if (_crep != 192) + displayEmptyHand(); + } + } +} + +/** + * Engine function - Listen + * @remarks Originally called 'tecouter' + */ +void MortevielleEngine::fctListen() { + if (_coreVar._currPlace != ROOM26) + _crep = 101; + else { + if (_currBitIndex != 0) + ++_coreVar._faithScore; + int rand; + int p = getPresenceStats(rand, _coreVar._faithScore, _roomDoorId); + int l = _roomDoorId; + if (l != OWN_ROOM) { + if (p != -500) { + if (rand > p) + _crep = 101; + else { + setPresenceFlags(l); + int j, h, m; + updateHour(j, h, m); + rand = getRandomNumber(1, 100); + if ((h >= 0) && (h < 8)) { + if (rand > 30) + _crep = 101; + else + _crep = 178; + } else if (rand > 70) + _crep = 101; + else + _crep = 178; + } + } else + _crep = 178; + } + } +} + +/** + * Engine function - Eat + * @remarks Originally called 'tmanger' + */ +void MortevielleEngine::fctEat() { + if ((_coreVar._currPlace > LANDING) && (_coreVar._currPlace < ROOM26)) { + _crep = 148; + } else { + exitRoom(); + _coreVar._currPlace = DINING_ROOM; + _caff = DINING_ROOM; + resetRoomVariables(_coreVar._currPlace); + _menu.setDestinationText(_coreVar._currPlace); + + int j, h, m; + updateHour(j, h, m); + if ((h == 12) || (h == 13) || (h == 19)) { + _coreVar._faithScore -= (_coreVar._faithScore / 7); + if (h == 12) { + if (m == 0) + h = 4; + else + h = 3; + } + + if ((h == 13) || (h == 19)) { + if (m == 0) + h = 2; + else + h = 1; + } + + _currentHourCount += h; + _crep = 135; + prepareRoom(); + } else { + _crep = 134; + } + } +} + +/** + * Engine function - Enter + * @remarks Originally called 'tentrer' + */ +void MortevielleEngine::fctEnter() { + if ((_coreVar._currPlace == MANOR_FRONT) || (_coreVar._currPlace == MANOR_BACK)) { + gotoDiningRoom(); + _menu.setDestinationText(_coreVar._currPlace); + } else if (_coreVar._currPlace == LANDING) + showMoveMenuAlert(); + else if (_roomDoorId == OWN_ROOM) + _crep = 997; + else if ((_roomDoorId == ROOM9) && (_coreVar._selectedObjectId != 136)) { + _crep = 189; + _coreVar._availableQuestion[8] = '*'; + } else { + int z = 0; + if (!_blo) + z = getPresence(_roomDoorId); + if (z != 0) { + if ((_roomDoorId == TOILETS) || (_roomDoorId == BATHROOM)) + _crep = 179; + else { + int randVal = (getRandomNumber(0, 10)) - 5; + _speechManager.startSpeech(7, randVal, 1); + displayAnimFrame(1, 1); + + int charIndex = convertBitIndexToCharacterIndex(z); + ++_coreVar._faithScore; + _coreVar._currPlace = LANDING; + _currMenu = MENU_DISCUSS; + _currAction = (_menu._discussMenu[charIndex]._menuId << 8) | _menu._discussMenu[charIndex]._actionId; + _syn = true; + if (_roomDoorId == ROOM9) { + _col = true; + _caff = 70; + drawPictureWithText(); + handleDescriptionText(2, _caff); + } else + _col = false; + resetRoomVariables(_roomDoorId); + _roomDoorId = OWN_ROOM; + } + } else { + int randVal = (getRandomNumber(0, 10)) - 5; + _speechManager.startSpeech(7, randVal, 1); + displayAnimFrame(1, 1); + + _coreVar._currPlace = _roomDoorId; + prepareDisplayText(); + resetRoomVariables(_coreVar._currPlace); + _menu.setDestinationText(_coreVar._currPlace); + _roomDoorId = OWN_ROOM; + _savedBitIndex = 0; + _currBitIndex = 0; + } + } +} + +/** + * Engine function - Sleep + * @remarks Originally called 'tdormir' + */ +void MortevielleEngine::fctSleep() { + int j, h, m; + + if ((_coreVar._currPlace > LANDING) && (_coreVar._currPlace < ROOM26)) { + _crep = 148; + return; + } + if (_coreVar._currPlace != OWN_ROOM) { + exitRoom(); + _coreVar._currPlace = OWN_ROOM; + prepareDisplayText(); + drawPictureWithText(); + resetRoomVariables(_coreVar._currPlace); + _menu.setDestinationText(_coreVar._currPlace); + } + clearVerbBar(); + clearDescriptionBar(); + prepareScreenType2(); + ecr2(getEngineString(S_WANT_TO_WAKE_UP)); + updateHour(j, h, m); + + int answer; + do { + if (h < 8) { + _coreVar._faithScore -= (_coreVar._faithScore / 20); + int z = (7 - h) * 2; + if (m == 30) + --z; + _currentHourCount += z; + h = 7; + } + _currentHourCount += 2; + ++h; + if (h > 23) + h = 0; + prepareRoom(); + answer = _dialogManager.show(getEngineString(S_YES_NO), 1); + _anyone = false; + } while (answer != 1); + _crep = 998; + _num = 0; +} + +/** + * Engine function - Force + * @remarks Originally called 'tdefoncer' + */ +void MortevielleEngine::fctForce() { + if (!_syn) + displayTextInVerbBar(getEngineString(S_SMASH)); + if (_caff < DOOR) + displayStatusArrow(); + + if ((!_anyone) && (!_keyPressedEsc)) { + if (_coreVar._currPlace != ROOM26) + _crep = 997; + else { + _crep = 143; + _coreVar._faithScore += 2; + } + } +} + +/** + * Engine function - Leave + * @remarks Originally called 'tsortir' + */ +void MortevielleEngine::fctLeave() { + exitRoom(); + _crep = 0; + if ((_coreVar._currPlace == MOUNTAIN) || (_coreVar._currPlace == MANOR_FRONT) || (_coreVar._currPlace == MANOR_BACK) || (_coreVar._currPlace == WELL)) + _crep = 997; + else { + int nextPlace = OWN_ROOM; + + if ((_coreVar._currPlace < CRYPT) || (_coreVar._currPlace == ROOM26)) + nextPlace = DINING_ROOM; + else if ((_coreVar._currPlace == DINING_ROOM) || (_coreVar._currPlace == CHAPEL)) + nextPlace = MANOR_FRONT; + else if ((_coreVar._currPlace < DINING_ROOM) || (_coreVar._currPlace == ATTIC)) + nextPlace = LANDING; + else if (_coreVar._currPlace == CRYPT) { + nextPlace = SECRET_PASSAGE; + _crep = 176; + } else if (_coreVar._currPlace == SECRET_PASSAGE) + nextPlace = checkLeaveSecretPassage(); + else if (_coreVar._currPlace == INSIDE_WELL) + nextPlace = WELL; + + if (_crep != 997) + _coreVar._currPlace = nextPlace; + + _caff = nextPlace; + if (_crep == 0) + _crep = nextPlace; + resetRoomVariables(nextPlace); + _menu.setDestinationText(nextPlace); + } +} + +/** + * Engine function - Wait + * @remarks Originally called 'tattendre' + */ +void MortevielleEngine::fctWait() { + _savedBitIndex = 0; + clearVerbBar(); + + int answer; + do { + ++_currentHourCount; + prepareRoom(); + if (!_blo) + getPresence(_coreVar._currPlace); + if ((_currBitIndex != 0) && (_savedBitIndex == 0)) { + _crep = 998; + if ((_coreVar._currPlace == ATTIC) || (_coreVar._currPlace == CELLAR)) + initCaveOrCellar(); + if ((_coreVar._currPlace > OWN_ROOM) && (_coreVar._currPlace < DINING_ROOM)) + _anyone = true; + _savedBitIndex = _currBitIndex; + if (!_anyone) + prepareRoom(); + return; + } + handleDescriptionText(2, 102); + answer = _dialogManager.show(getEngineString(S_YES_NO), 1); + } while (answer != 2); + _crep = 998; + if (!_anyone) + prepareRoom(); +} + +/** + * Engine function - Sound + * @remarks Originally called 'tsonder' + */ +void MortevielleEngine::fctSound() { + if (!_syn) + displayTextInVerbBar(getEngineString(S_PROBE2)); + if (_caff < 27) { + displayStatusArrow(); + if (!(_anyone) && (!_keyPressedEsc)) + _crep = 145; + _num = 0; + } +} + +/** + * Engine function - Discuss + * @remarks Originally called 'tparler' + */ +void MortevielleEngine::fctDiscuss() { + bool questionAsked[47]; + int cy, cx; + int x, y; + Common::String lib[47]; + + int choice; + int displId; + + endSearch(); + if (_col) + displId = 128; + else { + cx = 0; + int oldMenu; + do { + ++cx; + oldMenu = (_menu._discussMenu[cx]._menuId << 8) | _menu._discussMenu[cx]._actionId; + } while (oldMenu != _currAction); + _caff = 69 + cx; + drawPictureWithText(); + handleDescriptionText(2, _caff); + displId = _caff + 60; + } + testKey(false); + mennor(); + _mouse.hideMouse(); + hirs(); + premtet(); + startDialog(displId); + hirs(); + for (int ix = 1; ix <= 46; ++ix) + questionAsked[ix] = false; + for (int ix = 1; ix <= 45; ++ix) { + lib[ix] = getString(ix + kQuestionStringIndex); + for (int i = lib[ix].size(); i <= 40; ++i) + lib[ix] = lib[ix] + ' '; + } + lib[46] = lib[45]; + lib[45] = ' '; + _mouse.showMouse(); + do { + choice = 0; + int posX = 0; + int posY = 0; + for (int icm = 1; icm < 43; icm++) { + _screenSurface.putxy(posX, posY); + if (_coreVar._availableQuestion[icm] == '*') { + // If question already asked, write it in reverse video + if (questionAsked[icm]) + displayQuestionText(lib[icm], 1); + else + displayQuestionText(lib[icm], 0); + } + + if (icm == 23) { + posY = 0; + posX = 320; + } else + posY += 8; + } + _screenSurface.putxy(320, 176); + displayQuestionText(lib[46], 0); + char retKey = '\0'; + bool click; + do { + bool dummyFl; + _mouse.moveMouse(dummyFl, retKey); + if (shouldQuit()) + return; + + _mouse.getMousePosition(x, y, click); + x *= (3 - _resolutionScaler); + if (x > 319) + cx = 41; + else + cx = 1; + cy = ((uint)y >> 3) + 1; // 0-199 => 1-25 + if ((cy > 23) || ((cx == 41) && ((cy >= 20) && (cy <= 22)))) { + if (choice != 0) { + posY = ((choice - 1) % 23) << 3; + if (choice > 23) + posX = 320; + else + posX = 0; + _screenSurface.putxy(posX, posY); + if (questionAsked[choice]) + displayQuestionText(lib[choice], 0); + else + displayQuestionText(lib[choice], 1); + questionAsked[choice] = !questionAsked[choice]; + choice = 0; + } + } else { + int ix = cy; + if (cx == 41) + ix += 23; + if (ix != choice) { + if (choice != 0) { + posY = ((choice - 1) % 23) << 3; + if (choice > 23) + posX = 320; + else + posX = 0; + _screenSurface.putxy(posX, posY); + if (questionAsked[choice]) + displayQuestionText(lib[choice], 0); + else + displayQuestionText(lib[choice], 1); + questionAsked[choice] = ! questionAsked[choice]; + } + if ((_coreVar._availableQuestion[ix] == '*') || (ix == 46)) { + posY = ((ix - 1) % 23) << 3; + if (ix > 23) + posX = 320; + else + posX = 0; + _screenSurface.putxy(posX, posY); + if (questionAsked[ix]) + displayQuestionText(lib[ix], 0); + else + displayQuestionText(lib[ix], 1); + questionAsked[ix] = ! questionAsked[ix]; + choice = ix; + } else + choice = 0; + } + } + } while (!((retKey == '\15') || (((click != 0) || getMouseClick()) && (choice != 0)))); + setMouseClick(false); + + // If choice is not "End of Conversation" + if (choice != 46) { + int ix = choice - 1; + if (_col) { + _col = false; + _coreVar._currPlace = 15; + int maxRandVal; + if (_openObjCount > 0) + maxRandVal = 8; + else + maxRandVal = 4; + if (getRandomNumber(1, maxRandVal) == 2) + displId = 129; + else { + displId = 138; + _coreVar._faithScore += (3 * (_coreVar._faithScore / 10)); + } + } else if (_charAnswerCount[_caff - 69] < _charAnswerMax[_caff - 69]) { + displId = _tabdon[kArep + (ix << 3) + (_caff - 70)]; + _coreVar._faithScore += _tabdon[kArcf + ix]; + ++_charAnswerCount[_caff - 69]; + } else { + _coreVar._faithScore += 3; + displId = 139; + } + _mouse.hideMouse(); + hirs(); + premtet(); + startDialog(displId); + _mouse.showMouse(); + if ((displId == 84) || (displId == 86)) { + _coreVar._pctHintFound[5] = '*'; + _coreVar._availableQuestion[7] = '*'; + } + if ((displId == 106) || (displId == 108) || (displId == 94)) { + for (int indx = 29; indx <= 31; ++indx) + _coreVar._availableQuestion[indx] = '*'; + _coreVar._pctHintFound[7] = '*'; + } + if (displId == 70) { + _coreVar._pctHintFound[8] = '*'; + _coreVar._availableQuestion[32] = '*'; + } + _mouse.hideMouse(); + hirs(); + _mouse.showMouse(); + } + } while ((choice != 46) && (displId != 138)); + if (_col) { + _coreVar._faithScore += (3 * (_coreVar._faithScore / 10)); + _mouse.hideMouse(); + hirs(); + premtet(); + startDialog(138); + _mouse.showMouse(); + _col = false; + _coreVar._currPlace = LANDING; + } + _controlMenu = 0; + _mouse.hideMouse(); + hirs(); + drawRightFrame(); + _mouse.showMouse(); + showPeoplePresent(_currBitIndex); + prepareRoom(); + drawClock(); + prepareDisplayText(); + /* chech;*/ + _menu.setDestinationText(_coreVar._currPlace); + clearVerbBar(); +} + +/** + * Engine function - Smell + * @remarks Originally called 'tsentir' + */ +void MortevielleEngine::fctSmell() { + _crep = 119; + if (_caff < ROOM26) { + if (!_syn) + displayTextInVerbBar(getEngineString(S_SMELL)); + displayStatusArrow(); + if (!(_anyone) && !(_keyPressedEsc)) + if (_caff == CRYPT) + _crep = 153; + } else if (_caff == 123) + _crep = 110; + _num = 0; +} + +/** + * Engine function - Scratch + * @remarks Originally called 'tgratter' + */ +void MortevielleEngine::fctScratch() { + _crep = 155; + if (_caff < 27) { + if (!_syn) + displayTextInVerbBar(getEngineString(S_SCRATCH)); + displayStatusArrow(); + } + _num = 0; +} + +/** + * The game is over + * @remarks Originally called 'tmaj1' + */ +void MortevielleEngine::endGame() { + _quitGame = true; + tlu(13, 152); + displayEmptyHand(); + clearUpperLeftPart(); + clearDescriptionBar(); + clearVerbBar(); + handleDescriptionText(9, 1509); + testKey(false); + _mouse.hideMouse(); + _caff = 70; + _text.taffich(); + hirs(); + premtet(); + startDialog(141); + _mouse.showMouse(); + clearUpperLeftPart(); + handleDescriptionText(9, 1509); + handleDescriptionText(2, 142); + testKey(false); + _caff = 32; + drawPictureWithText(); + handleDescriptionText(6, 34); + handleDescriptionText(2, 35); + startMusicOrSpeech(0); + testKey(false); + // A wait message was displayed. + // testKey (aka tkey1) was called before and after. + // This double call is useless, thus removed + resetVariables(); +} + +/** + * You lost! + * @remarks Originally called 'tencore' + */ +void MortevielleEngine::askRestart() { + clearDescriptionBar(); + startMusicOrSpeech(0); + testKey(false); + displayEmptyHand(); + resetVariables(); + initGame(); + _currHour = 10; + _currHalfHour = 0; + _currDay = 0; + _minute = 0; + _hour = 10; + _day = 0; + handleDescriptionText(2, 180); + + int answer = _dialogManager.show(getEngineString(S_YES_NO), 1); + _quitGame = (answer != 1); +} + +} // End of namespace Mortevielle diff --git a/engines/mortevielle/debugger.cpp b/engines/mortevielle/debugger.cpp new file mode 100644 index 0000000000..4ef5151c81 --- /dev/null +++ b/engines/mortevielle/debugger.cpp @@ -0,0 +1,59 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "mortevielle/mortevielle.h" +#include "mortevielle/debugger.h" + +namespace Mortevielle { + +Debugger::Debugger() : GUI::Debugger() { + DCmd_Register("continue", WRAP_METHOD(Debugger, Cmd_Exit)); + DCmd_Register("show_questions", WRAP_METHOD(Debugger, Cmd_showAllQuestions)); + DCmd_Register("reset_parano", WRAP_METHOD(Debugger, Cmd_resetParano)); +} + +bool Debugger::Cmd_showAllQuestions(int argc, const char **argv) { + for (int i = 1; i <= 10; ++i) + _vm->_coreVar._pctHintFound[i] = '*'; + + for (int i = 1; i <= 42; ++i) + _vm->_coreVar._availableQuestion[i] = '*'; + + for (int i = 0; i < 9; i++) { + _vm->_charAnswerCount[i] = 0; + _vm->_charAnswerMax[i] = 999; + } + + return true; +} + +bool Debugger::Cmd_resetParano(int argc, const char **argv) { + _vm->_coreVar._faithScore = 0; + + return true; +} + +void Debugger::setParent(MortevielleEngine *vm) { + _vm = vm; +} + +} // End of namespace Mortevielle diff --git a/engines/mortevielle/debugger.h b/engines/mortevielle/debugger.h new file mode 100644 index 0000000000..9041d90110 --- /dev/null +++ b/engines/mortevielle/debugger.h @@ -0,0 +1,49 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef MORTEVIELLE_DEBUGGER_H +#define MORTEVIELLE_DEBUGGER_H + +#include "common/scummsys.h" +#include "gui/debugger.h" + +namespace Mortevielle { + +class MortevielleEngine; + +class Debugger : public GUI::Debugger { +private: + MortevielleEngine *_vm; + +protected: + bool Cmd_showAllQuestions(int argc, const char **argv); + bool Cmd_resetParano(int argc, const char **argv); + +public: + Debugger(); + virtual ~Debugger() {} + void setParent(MortevielleEngine *vm); +}; + +} // End of namespace Mortevielle + +#endif diff --git a/engines/mortevielle/detection.cpp b/engines/mortevielle/detection.cpp new file mode 100644 index 0000000000..28cbc77b8b --- /dev/null +++ b/engines/mortevielle/detection.cpp @@ -0,0 +1,101 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "base/plugins.h" +#include "engines/advancedDetector.h" + +#include "mortevielle/mortevielle.h" +#include "mortevielle/detection_tables.h" +#include "mortevielle/saveload.h" + +namespace Mortevielle { +uint32 MortevielleEngine::getGameFlags() const { return _gameDescription->flags; } + +Common::Language MortevielleEngine::getLanguage() const { return _gameDescription->language; } + +} + +static const PlainGameDescriptor MortevielleGame[] = { + {"mortevielle", "Mortville Manor"}, + {0, 0} +}; + +class MortevielleMetaEngine : public AdvancedMetaEngine { +public: + MortevielleMetaEngine() : AdvancedMetaEngine(Mortevielle::MortevielleGameDescriptions, sizeof(ADGameDescription), + MortevielleGame) { + _md5Bytes = 512; + _singleid = "mortevielle"; + } + virtual const char *getName() const { + return "Mortevielle"; + } + + virtual const char *getOriginalCopyright() const { + return "Mortville Manor (C) 1987-89 Lankhor"; + } + + virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; + virtual bool hasFeature(MetaEngineFeature f) const; + virtual int getMaximumSaveSlot() const; + virtual SaveStateList listSaves(const char *target) const; + virtual SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const; +}; + +bool MortevielleMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { + if (desc) { + *engine = new Mortevielle::MortevielleEngine(syst, desc); + } + return desc != 0; +} + +bool MortevielleMetaEngine::hasFeature(MetaEngineFeature f) const { + switch (f) { + case kSupportsListSaves: + case kSupportsDeleteSave: + case kSupportsLoadingDuringStartup: + case kSavesSupportMetaInfo: + case kSavesSupportThumbnail: + case kSavesSupportCreationDate: + return true; + default: + return false; + } +} + +int MortevielleMetaEngine::getMaximumSaveSlot() const { return 99; } + +SaveStateList MortevielleMetaEngine::listSaves(const char *target) const { + return Mortevielle::SavegameManager::listSaves(target); +} + +SaveStateDescriptor MortevielleMetaEngine::querySaveMetaInfos(const char *target, int slot) const { + Common::String filename = Mortevielle::MortevielleEngine::generateSaveFilename(target, slot); + return Mortevielle::SavegameManager::querySaveMetaInfos(filename); +} + + +#if PLUGIN_ENABLED_DYNAMIC(MORTEVIELLE) + REGISTER_PLUGIN_DYNAMIC(MORTEVIELLE, PLUGIN_TYPE_ENGINE, MortevielleMetaEngine); +#else + REGISTER_PLUGIN_STATIC(MORTEVIELLE, PLUGIN_TYPE_ENGINE, MortevielleMetaEngine); +#endif diff --git a/engines/mortevielle/detection_tables.h b/engines/mortevielle/detection_tables.h new file mode 100644 index 0000000000..689fee1222 --- /dev/null +++ b/engines/mortevielle/detection_tables.h @@ -0,0 +1,88 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +namespace Mortevielle { + +static const ADGameDescription MortevielleGameDescriptions[] = { + // French + { + "mortevielle", + "", + { + {"menufr.mor", 0, "e413f36b9e14eef16130adc347a9391f", 144}, + {"dxx.mor", 0, "949e68e829ecd5ad29e36a00347a9e7e", 207744}, + AD_LISTEND + }, + Common::FR_FRA, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO0() + }, + // French + { + "mortevielle", + "", + { + {"menu.mor", 0, "3fef0a3f8fca99fdcb6dbca8cbcef46f", 160}, + {"dxx.mor", 0, "949e68e829ecd5ad29e36a00347a9e7e", 207744}, + AD_LISTEND + }, + Common::FR_FRA, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO0() + }, + // German + { + "mortevielle", + "", + { + {"menual.mor", 0, "792aea282b07a1d74c4a4abeabc90c19", 144}, + {"dxx.mor", 0, "949e68e829ecd5ad29e36a00347a9e7e", 207744}, + AD_LISTEND + }, + Common::DE_DEU, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO0() + }, + + // English. Note that this is technically the French version, but English strings in mort.dat + // will automatically replace all the French strings + { + "mortevielle", + "", + { + {"menufr.mor", 0, "e413f36b9e14eef16130adc347a9391f", 144}, + {"dxx.mor", 0, "949e68e829ecd5ad29e36a00347a9e7e", 207744}, + AD_LISTEND + }, + Common::EN_ANY, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO0() + }, + + AD_TABLE_END_MARKER +}; + +} // End of namespace Mortevielle diff --git a/engines/mortevielle/dialogs.cpp b/engines/mortevielle/dialogs.cpp new file mode 100644 index 0000000000..ba5d984886 --- /dev/null +++ b/engines/mortevielle/dialogs.cpp @@ -0,0 +1,494 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This code is based on original Mortville Manor DOS source code + * Copyright (c) 1987-1989 Lankhor + */ + +#include "mortevielle/mortevielle.h" + +#include "mortevielle/dialogs.h" +#include "mortevielle/mouse.h" +#include "mortevielle/outtext.h" +#include "mortevielle/speech.h" + +#include "common/str.h" + +namespace Mortevielle { + +/** + * Alert function - Show + * @remarks Originally called 'do_alert' + */ +int DialogManager::show(const Common::String &msg, int n) { + // Make a copy of the current screen surface for later restore + _vm->_backgroundSurface.copyFrom(_vm->_screenSurface); + + _vm->_mouse.hideMouse(); + while (_vm->keyPressed()) + _vm->getChar(); + + _vm->setMouseClick(false); + + int colNumb = 0; + int lignNumb = 0; + int caseNumb = 0; + Common::String alertStr = ""; + Common::String caseStr; + + decodeAlertDetails(msg, caseNumb, lignNumb, colNumb, alertStr, caseStr); + + int i = 0; + Common::Point curPos; + if (alertStr == "") { + drawAlertBox(10, 5, colNumb); + } else { + drawAlertBox(8, 7, colNumb); + i = 0; + _vm->_screenSurface._textPos.y = 70; + do { + curPos.x = 320; + Common::String displayStr = ""; + while ((alertStr[i + 1] != '\174') && (alertStr[i + 1] != '\135')) { + ++i; + displayStr += alertStr[i]; + if (_vm->_resolutionScaler == 2) + curPos.x -= 3; + else + curPos.x -= 5; + } + _vm->_screenSurface.putxy(curPos.x, _vm->_screenSurface._textPos.y); + _vm->_screenSurface._textPos.y += 6; + _vm->_screenSurface.drawString(displayStr, 4); + ++i; + } while (alertStr[i] != ']'); + } + int esp; + if (caseNumb == 1) + esp = colNumb - 40; + else + esp = (uint)(colNumb - caseNumb * 40) / 2; + + int coldep = 320 - ((uint)colNumb / 2) + ((uint)esp / 2); + Common::String buttonStr[3]; + setButtonText(caseStr, coldep, caseNumb, &buttonStr[0], esp); + + int limit[3][3]; + memset(&limit[0][0], 0, sizeof(int) * 3 * 3); + + limit[1][1] = ((uint)(coldep) / 2) * _vm->_resolutionScaler; + limit[1][2] = limit[1][1] + 40; + if (caseNumb == 1) { + limit[2][1] = limit[2][2]; + } else { + limit[2][1] = ((uint)(320 + ((uint)esp >> 1)) / 2) * _vm->_resolutionScaler; + limit[2][2] = (limit[2][1]) + 40; + } + _vm->_mouse.showMouse(); + int id = 0; + bool dummyFl = false; + bool test3; + do { + char dummyKey = '\377'; + _vm->_mouse.moveMouse(dummyFl, dummyKey); + if (_vm->shouldQuit()) + return 0; + + curPos = _vm->_mouse._pos; + bool newaff = false; + if ((curPos.y > 95) && (curPos.y < 105)) { + bool test1 = (curPos.x > limit[1][1]) && (curPos.x < limit[1][2]); + bool test2 = test1; + if (caseNumb > 1) + test2 |= ((curPos.x > limit[2][1]) && (curPos.x < limit[2][2])); + if (test2) { + newaff = true; + + int ix; + if (test1) + ix = 1; + else + ix = 2; + if (ix != id) { + _vm->_mouse.hideMouse(); + if (id != 0) { + setPosition(id, coldep, esp); + + Common::String tmpStr(" "); + tmpStr += buttonStr[id]; + tmpStr += " "; + _vm->_screenSurface.drawString(tmpStr, 0); + } + setPosition(ix, coldep, esp); + + Common::String tmp2 = " "; + tmp2 += buttonStr[ix]; + tmp2 += " "; + _vm->_screenSurface.drawString(tmp2, 1); + + id = ix; + _vm->_mouse.showMouse(); + } + } + } + if ((id != 0) && !newaff) { + _vm->_mouse.hideMouse(); + setPosition(id, coldep, esp); + + Common::String tmp3(" "); + tmp3 += buttonStr[id]; + tmp3 += " "; + _vm->_screenSurface.drawString(tmp3, 0); + + id = 0; + _vm->_mouse.showMouse(); + } + test3 = (curPos.y > 95) && (curPos.y < 105) && (((curPos.x > limit[1][1]) && (curPos.x < limit[1][2])) + || ((curPos.x > limit[2][1]) && (curPos.x < limit[2][2]))); + } while (!_vm->getMouseClick()); + _vm->setMouseClick(false); + _vm->_mouse.hideMouse(); + if (!test3) { + id = n; + setPosition(n, coldep, esp); + Common::String tmp4(" "); + tmp4 += buttonStr[n]; + tmp4 += " "; + _vm->_screenSurface.drawString(tmp4, 1); + } + _vm->_mouse.showMouse(); + + /* Restore the background area */ + _vm->_screenSurface.copyFrom(_vm->_backgroundSurface, 0, 0); + + return id; +} + +/** + * Alert function - Decode Alert Details + * @remarks Originally called 'decod' + */ +void DialogManager::decodeAlertDetails(Common::String inputStr, int &choiceNumb, int &lineNumb, int &col, Common::String &choiceStr, Common::String &choiceListStr) { + // The second character of the string contains the number of choices + choiceNumb = atoi(inputStr.c_str() + 1); + + choiceStr = ""; + col = 0; + lineNumb = 0; + + // Originally set to 5, decreased to 4 because strings are 0 based, and not 1 based as in Pascal + int i = 4; + int k = 0; + bool empty = true; + + for (; inputStr[i] != ']'; ++i) { + choiceStr += inputStr[i]; + if ((inputStr[i] == '|') || (inputStr[i + 1] == ']')) { + if (k > col) + col = k; + k = 0; + ++lineNumb; + } else if (inputStr[i] != ' ') + empty = false; + ++k; + } + + if (empty) { + choiceStr = ""; + col = 20; + } else { + choiceStr += ']'; + col += 6; + } + + choiceListStr = Common::String(inputStr.c_str() + i); + if (_vm->_resolutionScaler == 2) + col *= 6; + else + col *= 10; +} + +void DialogManager::setPosition(int ji, int coldep, int esp) { + _vm->_screenSurface.putxy(coldep + (40 + esp) * (ji - 1), 98); +} + +/** + * Alert function - Draw Alert Box + * @remarks Originally called 'fait_boite' + */ +void DialogManager::drawAlertBox(int lidep, int nli, int tx) { + if (tx > 640) + tx = 640; + int x = 320 - ((uint)tx / 2); + int y = (lidep - 1) * 8; + int xx = x + tx; + int yy = y + (nli * 8); + _vm->_screenSurface.fillRect(15, Common::Rect(x, y, xx, yy)); + _vm->_screenSurface.fillRect(0, Common::Rect(x, y + 2, xx, y + 4)); + _vm->_screenSurface.fillRect(0, Common::Rect(x, yy - 4, xx, yy - 2)); +} + +/** + * Alert function - Set Button Text + * @remarks Originally called 'fait_choix' + */ +void DialogManager::setButtonText(Common::String c, int coldep, int nbcase, Common::String *str, int esp) { + int i = 1; + int x = coldep; + for (int l = 1; l <= nbcase; ++l) { + str[l] = ""; + do { + ++i; + char ch = c[i]; + str[l] += ch; + } while (c[i + 1] != ']'); + i += 2; + + while (str[l].size() < 3) + str[l] += ' '; + + _vm->_screenSurface.putxy(x, 98); + + Common::String tmp(" "); + tmp += str[l]; + tmp += " "; + + _vm->_screenSurface.drawString(tmp, 0); + x += esp + 40; + } +} + +/*------------------------------------------------------------------------*/ + +/** + * Questions asked before entering the hidden passage + */ +bool DialogManager::showKnowledgeCheck() { + const int textIndexArr[10] = {511, 516, 524, 531, 545, 552, 559, 563, 570, 576}; + const int correctAnswerArr[10] = {4, 7, 1, 6, 4, 4, 2, 5, 3, 1 }; + + Hotspot coor[kMaxHotspots+1]; + + for (int i = 0; i <= kMaxHotspots; ++i) { + coor[i]._rect = Common::Rect(); + coor[i]._enabled = false; + } + + Common::String choiceArray[15]; + + int currChoice, prevChoice; + int correctCount = 0; + + for (int indx = 0; indx < 10; ++indx) { + _vm->_mouse.hideMouse(); + _vm->hirs(); + _vm->_mouse.showMouse(); + int dialogHeight; + if (_vm->_resolutionScaler == 1) + dialogHeight = 29; + else + dialogHeight = 23; + _vm->_screenSurface.fillRect(15, Common::Rect(0, 14, 630, dialogHeight)); + Common::String tmpStr = _vm->getString(textIndexArr[indx]); + _vm->_text.displayStr(tmpStr, 20, 15, 100, 2, 0); + + int firstOption; + int lastOption; + + if (indx != 9) { + firstOption = textIndexArr[indx] + 1; + lastOption = textIndexArr[indx + 1] - 1; + } else { + firstOption = 503; + lastOption = 510; + } + int optionPosY = 35; + int maxLength = 0; + + prevChoice = 1; + for (int j = firstOption; j <= lastOption; ++j, ++prevChoice) { + tmpStr = _vm->getString(j); + if ((int) tmpStr.size() > maxLength) + maxLength = tmpStr.size(); + _vm->_text.displayStr(tmpStr, 100, optionPosY, 100, 1, 0); + choiceArray[prevChoice] = tmpStr; + optionPosY += 8; + } + + for (int j = 1; j <= lastOption - firstOption + 1; ++j) { + coor[j]._rect = Common::Rect(45 * _vm->_resolutionScaler, 27 + j * 8, (maxLength * 3 + 55) * _vm->_resolutionScaler, 34 + j * 8); + coor[j]._enabled = true; + + while ((int)choiceArray[j].size() < maxLength) { + choiceArray[j] += ' '; + } + } + coor[lastOption - firstOption + 2]._enabled = false; + int rep; + if (_vm->_resolutionScaler == 1) + rep = 10; + else + rep = 6; + _vm->_screenSurface.drawBox(80, 33, 40 + (maxLength * rep), (lastOption - firstOption) * 8 + 16, 15); + rep = 0; + + prevChoice = 0; + warning("Expected answer: %d", correctAnswerArr[indx]); + do { + _vm->setMouseClick(false); + bool flag; + char key; + _vm->_mouse.moveMouse(flag, key); + if (_vm->shouldQuit()) + return false; + + currChoice = 1; + while (coor[currChoice]._enabled && !_vm->_mouse.isMouseIn(coor[currChoice]._rect)) + ++currChoice; + if (coor[currChoice]._enabled) { + if ((prevChoice != 0) && (prevChoice != currChoice)) { + tmpStr = choiceArray[prevChoice] + '$'; + _vm->_text.displayStr(tmpStr, 100, 27 + (prevChoice * 8), 100, 1, 0); + } + if (prevChoice != currChoice) { + tmpStr = choiceArray[currChoice] + '$'; + _vm->_text.displayStr(tmpStr, 100, 27 + (currChoice * 8), 100, 1, 1); + prevChoice = currChoice; + } + } else if (prevChoice != 0) { + tmpStr = choiceArray[prevChoice] + '$'; + _vm->_text.displayStr(tmpStr, 100, 27 + (prevChoice * 8), 100, 1, 0); + prevChoice = 0; + } + } while (!((prevChoice != 0) && _vm->getMouseClick())); + + if (prevChoice == correctAnswerArr[indx]) + // Answer is correct + ++correctCount; + else { + // Skip questions that may give hints on previous wrong answer + if (indx == 4) + ++indx; + else if ((indx == 6) || (indx == 7)) + indx = 9; + } + } + + return (correctCount == 10); +} + +/*------------------------------------------------------------------------*/ + +/** + * Draw the F3/F8 dialog + */ +void DialogManager::drawF3F8() { + Common::String f3 = _vm->getEngineString(S_F3); + Common::String f8 = _vm->getEngineString(S_F8); + + // Write the F3 and F8 text strings + _vm->_screenSurface.putxy(3, 44); + _vm->_screenSurface.drawString(f3, 5); + _vm->_screenSurface._textPos.y = 51; + _vm->_screenSurface.drawString(f8, 5); + + // Get the width of the written text strings + int f3Width = _vm->_screenSurface.getStringWidth(f3); + int f8Width = _vm->_screenSurface.getStringWidth(f8); + + // Write out the bounding box + _vm->_screenSurface.drawBox(0, 42, MAX(f3Width, f8Width) + 6, 18, 7); +} + +/** + * Alert function - Loop until F8 is pressed, update + * Graphical Device if modified + * @remarks Originally called 'diver' + */ +void DialogManager::checkForF8(int SpeechNum, bool drawFrame2Fl) { + _vm->testKeyboard(); + do { + _vm->_speechManager.startSpeech(SpeechNum, 0, 0); + _vm->_key = waitForF3F8(); + if (_vm->shouldQuit()) + return; + + if (_vm->_newGraphicalDevice != _vm->_currGraphicalDevice) { + _vm->_currGraphicalDevice = _vm->_newGraphicalDevice; + _vm->hirs(); + displayIntroScreen(drawFrame2Fl); + } + } while (_vm->_key != 66); // keycode for F8 +} + +/** + * Alert function - Loop until F3 or F8 is pressed + * @remarks Originally called 'atf3f8' + */ +int DialogManager::waitForF3F8() { + int key; + + do { + key = _vm->gettKeyPressed(); + if (_vm->shouldQuit()) + return key; + } while ((key != 61) && (key != 66)); + + return key; +} + +/** + * Intro function - display intro screen + * @remarks Originally called 'aff50' + */ +void DialogManager::displayIntroScreen(bool drawFrame2Fl) { + _vm->_caff = 50; + _vm->_maff = 0; + _vm->_text.taffich(); + _vm->draw(63, 12); + if (drawFrame2Fl) + displayIntroFrame2(); + else + _vm->handleDescriptionText(2, kDialogStringIndex + 142); + + // Draw the f3/f8 dialog + drawF3F8(); +} + +/** + * Intro function - display 2nd frame of intro + * @remarks Originally called 'ani50' + */ +void DialogManager::displayIntroFrame2() { + _vm->_crep = _vm->getAnimOffset(1, 1); + _vm->displayPicture(&_vm->_curAnim[_vm->_crep], 63, 12); + _vm->_crep = _vm->getAnimOffset(2, 1); + _vm->displayPicture(&_vm->_curAnim[_vm->_crep], 63, 12); + _vm->_largestClearScreen = (_vm->_resolutionScaler == 1); + _vm->handleDescriptionText(2, kDialogStringIndex + 143); +} + +void DialogManager::setParent(MortevielleEngine *vm) { + _vm = vm; +} +} // End of namespace Mortevielle diff --git a/engines/mortevielle/dialogs.h b/engines/mortevielle/dialogs.h new file mode 100644 index 0000000000..af667e40c5 --- /dev/null +++ b/engines/mortevielle/dialogs.h @@ -0,0 +1,65 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This code is based on original Mortville Manor DOS source code + * Copyright (c) 1987-1989 Lankhor + */ + +#ifndef MORTEVIELLE_ALERT_H +#define MORTEVIELLE_ALERT_H + +#include "common/rect.h" +#include "common/str.h" + +namespace Mortevielle { +class MortevielleEngine; + +static const int NUM_LINES = 7; +const int kMaxHotspots = 14; + +struct Hotspot { + Common::Rect _rect; + bool _enabled; +}; + +class DialogManager { +private: + MortevielleEngine *_vm; + + void decodeAlertDetails(Common::String inputStr, int &choiceNumb, int &lineNumb, int &col, Common::String &choiceStr, Common::String &choiceListStr); + void setPosition(int ji, int coldep, int esp); + void drawAlertBox(int lidep, int nli, int tx); + void setButtonText(Common::String c, int coldep, int nbcase, Common::String *str, int esp); +public: + void setParent(MortevielleEngine *vm); + int show(const Common::String &msg, int n); + void drawF3F8(); + void checkForF8(int SpeechNum, bool drawFrame2Fl); + int waitForF3F8(); + void displayIntroScreen(bool drawFrame2Fl); + void displayIntroFrame2(); + bool showKnowledgeCheck(); +}; + +} // End of namespace Mortevielle +#endif diff --git a/engines/mortevielle/graphics.cpp b/engines/mortevielle/graphics.cpp new file mode 100644 index 0000000000..8392fabd6a --- /dev/null +++ b/engines/mortevielle/graphics.cpp @@ -0,0 +1,1187 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This code is based on original Mortville Manor DOS source code + * Copyright (c) 1987-1989 Lankhor + */ + +#include "mortevielle/mortevielle.h" +#include "mortevielle/graphics.h" +#include "mortevielle/mouse.h" + +#include "common/endian.h" +#include "common/system.h" +#include "graphics/palette.h" + +namespace Mortevielle { + +/*-------------------------------------------------------------------------* + * Palette Manager + * + *-------------------------------------------------------------------------*/ + +/** + * Set palette entries from the 64 color available EGA palette + */ +void PaletteManager::setPalette(const int *palette, uint idx, uint size) { + assert((idx + size) <= 16); + + // Build up the EGA palette + byte egaPalette[64 * 3]; + + byte *p = &egaPalette[0]; + for (int i = 0; i < 64; ++i) { + *p++ = (i >> 2 & 1) * 0xaa + (i >> 5 & 1) * 0x55; + *p++ = (i >> 1 & 1) * 0xaa + (i >> 4 & 1) * 0x55; + *p++ = (i & 1) * 0xaa + (i >> 3 & 1) * 0x55; + } + + // Loop through setting palette colors based on the passed indexes + for (; size > 0; --size, ++idx) { + int palIndex = palette[idx]; + assert(palIndex < 64); + + const byte *pRgb = (const byte *)&egaPalette[palIndex * 3]; + g_system->getPaletteManager()->setPalette(pRgb, idx, 1); + } +} + +/** + * Set the default EGA palette + */ +void PaletteManager::setDefaultPalette() { + const int defaultPalette[16] = { 0, 1, 2, 3, 4, 5, 20, 7, 56, 57, 58, 59, 60, 61, 62, 63 }; + setPalette(defaultPalette, 0, 16); +} + +/*-------------------------------------------------------------------------* + * Image decoding + * + * The code in this section is responsible for decoding image resources. + * Images are broken down into rectangular sections, which can use one + * of 18 different encoding methods. + *-------------------------------------------------------------------------*/ + +#define INCR_XSIZE { if (_xSize & 1) ++_xSize; } +#define DEFAULT_WIDTH (SCREEN_WIDTH / 2) +#define BUFFER_SIZE 40000 + +void GfxSurface::decode(const byte *pSrc) { + w = h = 0; + // If no transparency, use invalid (for EGA) palette index of 16. Otherwise get index to use + _transparency = (*pSrc == 0) ? 16 : *(pSrc + 2); + bool offsetFlag = *pSrc++ == 0; + int entryCount = *pSrc++; + pSrc += 2; + + if (offsetFlag) + pSrc += 30; + + // First run through the data to calculate starting offsets + const byte *p = pSrc; + _offset.x = _offset.y = 999; + + assert(entryCount > 0); + for (int idx = 0; idx < entryCount; ++idx) { + _xp = READ_BE_UINT16(p + 4); + if (_xp < _offset.x) + _offset.x = _xp; + + _yp = READ_BE_UINT16(p + 6); + if (_yp < _offset.y) + _offset.y = _yp; + + // Move to next entry + int size = READ_BE_UINT16(p) + READ_BE_UINT16(p + 2); + if ((size % 2) == 1) + ++size; + + p += size + 14; + } + + // Temporary output buffer + byte *outputBuffer = (byte *)malloc(sizeof(byte) * 65536); + memset(outputBuffer, _transparency, 65536); + + byte *pDest = &outputBuffer[0]; + const byte *pSrcStart = pSrc; + const byte *pLookup = NULL; + + byte *lookupTable = (byte *)malloc(sizeof(byte) * BUFFER_SIZE); + byte *srcBuffer = (byte *)malloc(sizeof(byte) * BUFFER_SIZE); + + // Main processing loop + for (int entryIndex = 0; entryIndex < entryCount; ++entryIndex) { + int lookupBytes = READ_BE_UINT16(pSrc); + int srcSize = READ_BE_UINT16(pSrc + 2); + _xp = READ_BE_UINT16(pSrc + 4) - _offset.x; + _yp = READ_BE_UINT16(pSrc + 6) - _offset.y; + assert((_xp >= 0) && (_yp >= 0) && (_xp < SCREEN_WIDTH) && (_yp < SCREEN_ORIG_HEIGHT)); + pSrc += 8; + + int decomCode = READ_BE_UINT16(pSrc); + _xSize = READ_BE_UINT16(pSrc + 2) + 1; + _ySize = READ_BE_UINT16(pSrc + 4) + 1; + majTtxTty(); + + pSrc += 6; + pDest = &outputBuffer[0]; + + _lookupIndex = 0; + _nibbleFlag = false; + + int decomIndex = 0; + if (decomCode >> 8) { + // Build up reference table + int tableOffset = 0; + + if (decomCode & 1) { + // Handle decompression of the pattern lookup table + do { + int outerCount = desanalyse(pSrc); + int innerCount = desanalyse(pSrc); + + const byte *pSrcSaved = pSrc; + bool savedNibbleFlag = _nibbleFlag; + int savedLookupIndex = _lookupIndex; + + do { + pSrc = pSrcSaved; + _nibbleFlag = savedNibbleFlag; + _lookupIndex = savedLookupIndex; + + for (int idx = 0; idx < innerCount; ++idx, ++tableOffset) { + assert(tableOffset < BUFFER_SIZE); + lookupTable[tableOffset] = nextNibble(pSrc); + } + } while (--outerCount > 0); + } while (_lookupIndex < (lookupBytes - 1)); + + } else { + assert(lookupBytes < BUFFER_SIZE); + for (int idx = 0; idx < (lookupBytes * 2); ++idx) + lookupTable[idx] = nextNibble(pSrc); + } + + if (_nibbleFlag) { + ++pSrc; + _nibbleFlag = false; + } + if ((lookupBytes + srcSize) & 1) + ++pSrc; + + tableOffset = 0; + _lookupIndex = 0; + + if (decomCode & 2) { + // Handle decompression of the temporary source buffer + do { + int outerCount = desanalyse(pSrc); + int innerCount = desanalyse(pSrc); + _lookupIndex += innerCount; + + if (_nibbleFlag) { + ++pSrc; + ++_lookupIndex; + _nibbleFlag = false; + } + + const byte *pStart = pSrc; + do { + pSrc = pStart; + for (int idx = 0; idx < innerCount; ++idx) { + assert(tableOffset < BUFFER_SIZE); + srcBuffer[tableOffset++] = *pSrc++; + } + } while (--outerCount > 0); + } while (_lookupIndex < (srcSize - 1)); + } else { + assert(srcSize < BUFFER_SIZE); + for (int idx = 0; idx < srcSize; ++idx) + srcBuffer[idx] = *pSrc++; + } + + if (_nibbleFlag) + ++pSrc; + + // Switch over to using the decompressed source and lookup buffers + pSrcStart = pSrc; + pDest = &outputBuffer[_yp * DEFAULT_WIDTH + _xp]; + pSrc = &srcBuffer[0]; + pLookup = &lookupTable[0] - 1; + + _lookupValue = _lookupIndex = 0; + _nibbleFlag = false; + decomIndex = decomCode >> 8; + } + + // Main decompression switch + switch (decomIndex) { + case 0: + // Draw rect at pos + pDest = &outputBuffer[_yp * DEFAULT_WIDTH + _xp]; + pSrcStart = pSrc; + INCR_XSIZE; + + for (int yCtr = 0; yCtr < _ySize; ++yCtr, pDest += DEFAULT_WIDTH) { + byte *pDestLine = pDest; + for (int xCtr = 0; xCtr < _xSize; ++xCtr) { + *pDestLine++ = nextNibble(pSrc); + } + } + + pSrcStart += lookupBytes + ((lookupBytes & 1) ? 1 : 0); + break; + + case 1: + // Draw rect using horizontal lines alternating left to right, then right to left + INCR_XSIZE; + for (int yCtr = 0; yCtr < _ySize; ++yCtr) { + if ((yCtr % 2) == 0) { + for (int xCtr = 0; xCtr < _xSize; ++xCtr) { + *pDest++ = nextByte(pSrc, pLookup); + } + } else { + for (int xCtr = 0; xCtr < _xSize; ++xCtr) { + *--pDest = nextByte(pSrc, pLookup); + } + } + pDest += DEFAULT_WIDTH; + } + break; + + case 2: + // Draw rect alternating top to bottom, bottom to top + for (int xCtr = 0; xCtr < _xSize; ++xCtr) { + if ((xCtr % 2) == 0) { + for (int yCtr = 0; yCtr < _ySize; ++yCtr, pDest += DEFAULT_WIDTH) { + *pDest = nextByte(pSrc, pLookup); + } + } else { + for (int yCtr = 0; yCtr < _ySize; ++yCtr) { + pDest -= DEFAULT_WIDTH; + *pDest = nextByte(pSrc, pLookup); + } + } + ++pDest; + } + break; + + case 3: + // Draw horizontal area? + _thickness = 2; + horizontal(pSrc, pDest, pLookup); + break; + + case 4: + // Draw vertical area? + _thickness = 2; + vertical(pSrc, pDest, pLookup); + break; + + case 5: + _thickness = 3; + horizontal(pSrc, pDest, pLookup); + break; + + case 6: + _thickness = 4; + vertical(pSrc, pDest, pLookup); + break; + + case 7: + // Draw rect using horizontal lines left to right + INCR_XSIZE; + for (int yCtr = 0; yCtr < _ySize; ++yCtr, pDest += DEFAULT_WIDTH) { + byte *pDestLine = pDest; + for (int xCtr = 0; xCtr < _xSize; ++xCtr) + *pDestLine++ = nextByte(pSrc, pLookup); + } + break; + + case 8: + // Draw box + for (int xCtr = 0; xCtr < _xSize; ++xCtr, ++pDest) { + byte *pDestLine = pDest; + for (int yCtr = 0; yCtr < _ySize; ++yCtr, pDestLine += DEFAULT_WIDTH) + *pDestLine = nextByte(pSrc, pLookup); + } + break; + + case 9: + _thickness = 4; + horizontal(pSrc, pDest, pLookup); + break; + + case 10: + _thickness = 6; + horizontal(pSrc, pDest, pLookup); + break; + + case 11: + decom11(pSrc, pDest, pLookup); + break; + + case 12: + INCR_XSIZE; + _thickness = _xInc = 1; + _yInc = DEFAULT_WIDTH; + _yEnd = _ySize; + _xEnd = _xSize; + diag(pSrc, pDest, pLookup); + break; + + case 13: + INCR_XSIZE; + _thickness = _xSize; + _yInc = 1; + _yEnd = _xSize; + _xInc = DEFAULT_WIDTH; + _xEnd = _ySize; + diag(pSrc, pDest, pLookup); + break; + + case 14: + _thickness = _yInc = 1; + _yEnd = _xSize; + _xInc = DEFAULT_WIDTH; + _xEnd = _ySize; + diag(pSrc, pDest, pLookup); + break; + + case 15: + INCR_XSIZE; + _thickness = 2; + _yInc = DEFAULT_WIDTH; + _yEnd = _ySize; + _xInc = 1; + _xEnd = _xSize; + diag(pSrc, pDest, pLookup); + break; + + case 16: + _thickness = 3; + _yInc = 1; + _yEnd = _xSize; + _xInc = DEFAULT_WIDTH; + _xEnd = _ySize; + diag(pSrc, pDest, pLookup); + break; + + case 17: + INCR_XSIZE; + _thickness = 3; + _yInc = DEFAULT_WIDTH; + _yEnd = _ySize; + _xInc = 1; + _xEnd = _xSize; + diag(pSrc, pDest, pLookup); + break; + + case 18: + INCR_XSIZE; + _thickness = 5; + _yInc = DEFAULT_WIDTH; + _yEnd = _ySize; + _xInc = 1; + _xEnd = _xSize; + diag(pSrc, pDest, pLookup); + break; + + default: + error("Unknown decompression block type %d", decomIndex); + } + + pSrc = pSrcStart; + debugC(2, kMortevielleGraphics, "Decoding image block %d position %d,%d size %d,%d method %d", + entryIndex + 1, _xp, _yp, w, h, decomIndex); + } + + // At this point, the outputBuffer has the data for the image. Initialize the surface + // with the calculated size, and copy the lines to the surface + create(w, h, Graphics::PixelFormat::createFormatCLUT8()); + + for (int yCtr = 0; yCtr < h; ++yCtr) { + const byte *copySrc = &outputBuffer[yCtr * DEFAULT_WIDTH]; + byte *copyDest = (byte *)getBasePtr(0, yCtr); + + Common::copy(copySrc, copySrc + w, copyDest); + } + + ::free(outputBuffer); + ::free(lookupTable); + ::free(srcBuffer); +} + +void GfxSurface::majTtxTty() { + if (!_yp) + w += _xSize; + + if (!_xp) + h += _ySize; +} + +/** + * Decompression Function - get next nibble + * @remarks Originally called 'suiv' + */ +byte GfxSurface::nextNibble(const byte *&pSrc) { + int v = *pSrc; + if (_nibbleFlag) { + ++pSrc; + ++_lookupIndex; + _nibbleFlag = false; + return v & 0xf; + } else { + _nibbleFlag = !_nibbleFlag; + return v >> 4; + } +} + +/** + * Decompression Function - get next byte + * @remarks Originally called 'csuiv' + */ +byte GfxSurface::nextByte(const byte *&pSrc, const byte *&pLookup) { + assert(pLookup); + + while (!_lookupValue) { + int v; + do { + v = nextNibble(pSrc) & 0xff; + _lookupValue += v; + } while (v == 0xf); + ++pLookup; + } + + --_lookupValue; + return *pLookup; +} + +int GfxSurface::desanalyse(const byte *&pSrc) { + int total = 0; + int v = nextNibble(pSrc); + if (v == 0xf) { + int v2; + do { + v2 = nextNibble(pSrc); + total += v2; + } while (v2 == 0xf); + + total *= 15; + v = nextNibble(pSrc); + } + + total += v; + return total; +} + +void GfxSurface::horizontal(const byte *&pSrc, byte *&pDest, const byte *&pLookup) { + INCR_XSIZE; + byte *pDestEnd = pDest + (_ySize - 1) * DEFAULT_WIDTH + _xSize; + + for (;;) { + // If position is past end point, then skip this line + if (((_thickness - 1) * DEFAULT_WIDTH) + pDest >= pDestEnd) { + if (--_thickness == 0) + break; + continue; + } + + bool continueFlag = false; + do { + for (int xIndex = 0; xIndex < _xSize; ++xIndex) { + if ((xIndex % 2) == 0) { + if (xIndex != 0) + ++pDest; + + // Write out vertical slice top to bottom + for (int yIndex = 0; yIndex < _thickness; ++yIndex, pDest += DEFAULT_WIDTH) + *pDest = nextByte(pSrc, pLookup); + + ++pDest; + } else { + // Write out vertical slice bottom to top + for (int yIndex = 0; yIndex < _thickness; ++yIndex) { + pDest -= DEFAULT_WIDTH; + *pDest = nextByte(pSrc, pLookup); + } + } + } + + if ((_xSize % 2) == 0) { + int blockSize = _thickness * DEFAULT_WIDTH; + pDest += blockSize; + blockSize -= DEFAULT_WIDTH; + + if (pDestEnd < (pDest + blockSize)) { + do { + if (--_thickness == 0) + return; + } while ((pDest + (_thickness - 1) * DEFAULT_WIDTH) >= pDestEnd); + } + } else { + while ((pDest + (_thickness - 1) * DEFAULT_WIDTH) >= pDestEnd) { + if (--_thickness == 0) + return; + } + } + + for (int xIndex = 0; xIndex < _xSize; ++xIndex, --pDest) { + if ((xIndex % 2) == 0) { + // Write out vertical slice top to bottom + for (int yIndex = 0; yIndex < _thickness; ++yIndex, pDest += DEFAULT_WIDTH) + *pDest = nextByte(pSrc, pLookup); + } else { + // Write out vertical slice top to bottom + for (int yIndex = 0; yIndex < _thickness; ++yIndex) { + pDest -= DEFAULT_WIDTH; + *pDest = nextByte(pSrc, pLookup); + } + } + } + + if ((_xSize % 2) == 1) { + ++pDest; + + if ((pDest + (_thickness - 1) * DEFAULT_WIDTH) < pDestEnd) { + continueFlag = true; + break; + } + } else { + pDest += _thickness * DEFAULT_WIDTH + 1; + continueFlag = true; + break; + } + + ++pDest; + } while (((_thickness - 1) * DEFAULT_WIDTH + pDest) < pDestEnd); + + if (continueFlag) + continue; + + // Move to next line + if (--_thickness == 0) + break; + } +} + +void GfxSurface::vertical(const byte *&pSrc, byte *&pDest, const byte *&pLookup) { + int drawIndex = 0; + + for (;;) { + // Reduce thickness as necessary + while ((drawIndex + _thickness) > _xSize) { + if (--_thickness == 0) + return; + } + + // Loop + for (int yCtr = 0; yCtr < _ySize; ++yCtr) { + if ((yCtr % 2) == 0) { + if (yCtr > 0) + pDest += DEFAULT_WIDTH; + + drawIndex += _thickness; + for (int xCtr = 0; xCtr < _thickness; ++xCtr) + *pDest++ = nextByte(pSrc, pLookup); + } else { + pDest += DEFAULT_WIDTH; + drawIndex -= _thickness; + for (int xCtr = 0; xCtr < _thickness; ++xCtr) + *--pDest = nextByte(pSrc, pLookup); + } + } + if ((_ySize % 2) == 0) { + pDest += _thickness; + drawIndex += _thickness; + } + + while (_xSize < (drawIndex + _thickness)) { + if (--_thickness == 0) + return; + } + + // Loop + for (int yCtr = 0; yCtr < _ySize; ++yCtr) { + if ((yCtr % 2) == 0) { + if (yCtr > 0) + pDest -= DEFAULT_WIDTH; + + drawIndex += _thickness; + + for (int xCtr = 0; xCtr < _thickness; ++xCtr) + *pDest++ = nextByte(pSrc, pLookup); + } else { + pDest -= DEFAULT_WIDTH; + drawIndex -= _thickness; + + for (int xCtr = 0; xCtr < _thickness; ++xCtr) + *--pDest = nextByte(pSrc, pLookup); + } + } + if ((_ySize % 2) == 0) { + pDest += _thickness; + drawIndex += _thickness; + } + } +} + +void GfxSurface::decom11(const byte *&pSrc, byte *&pDest, const byte *&pLookup) { + int yPos = 0, drawIndex = 0; + _yInc = DEFAULT_WIDTH; + _xInc = -1; + --_xSize; + --_ySize; + + int areaNum = 0; + while (areaNum != -1) { + switch (areaNum) { + case 0: + *pDest = nextByte(pSrc, pLookup); + areaNum = 1; + break; + + case 1: + nextDecompPtr(pDest); + + if (!drawIndex) { + negXInc(); + negYInc(); + + if (yPos == _ySize) { + nextDecompPtr(pDest); + ++drawIndex; + } else { + ++yPos; + } + + *++pDest = nextByte(pSrc, pLookup); + areaNum = 2; + } else if (yPos != _ySize) { + ++yPos; + --drawIndex; + areaNum = 0; + } else { + negXInc(); + negYInc(); + nextDecompPtr(pDest); + ++drawIndex; + + *++pDest = nextByte(pSrc, pLookup); + + if (drawIndex == _xSize) { + areaNum = -1; + } else { + areaNum = 2; + } + } + break; + + case 2: + nextDecompPtr(pDest); + + if (!yPos) { + negXInc(); + negYInc(); + + if (drawIndex == _xSize) { + nextDecompPtr(pDest); + ++yPos; + } else { + ++drawIndex; + } + + pDest += DEFAULT_WIDTH; + areaNum = 0; + } else if (drawIndex != _xSize) { + ++drawIndex; + --yPos; + + *pDest = nextByte(pSrc, pLookup); + areaNum = 2; + } else { + pDest += DEFAULT_WIDTH; + ++yPos; + negXInc(); + negYInc(); + nextDecompPtr(pDest); + + *pDest = nextByte(pSrc, pLookup); + + if (yPos == _ySize) + areaNum = -1; + else + areaNum = 1; + } + break; + } + } +} + +void GfxSurface::diag(const byte *&pSrc, byte *&pDest, const byte *&pLookup) { + int diagIndex = 0, drawIndex = 0; + --_xEnd; + + while (!TFP(diagIndex)) { + for (;;) { + negXInc(); + for (int idx = 0; idx <= _thickness; ++idx) { + *pDest = nextByte(pSrc, pLookup); + negXInc(); + nextDecompPtr(pDest); + } + + negYInc(); + pDest += _yInc; + + for (int idx = 0; idx <= _thickness; ++idx) { + *pDest = nextByte(pSrc, pLookup); + negXInc(); + nextDecompPtr(pDest); + } + + negXInc(); + negYInc(); + nextDecompPtr(pDest); + + ++drawIndex; + if (_xEnd < (drawIndex + 1)) { + TF1(pDest, diagIndex); + break; + } + + pDest += _xInc; + ++drawIndex; + if (_xEnd < (drawIndex + 1)) { + TF2(pSrc, pDest, pLookup, diagIndex); + break; + } + } + + if (TFP(diagIndex)) + break; + + for (;;) { + for (int idx = 0; idx <= _thickness; ++idx) { + *pDest = nextByte(pSrc, pLookup); + negXInc(); + nextDecompPtr(pDest); + } + + negYInc(); + pDest += _yInc; + + for (int idx = 0; idx <= _thickness; ++idx) { + *pDest = nextByte(pSrc, pLookup); + negXInc(); + nextDecompPtr(pDest); + } + + negXInc(); + negYInc(); + nextDecompPtr(pDest); + + if (--drawIndex == 0) { + TF1(pDest, diagIndex); + negXInc(); + break; + } else { + pDest += _xInc; + + if (--drawIndex == 0) { + TF2(pSrc, pDest, pLookup, diagIndex); + negXInc(); + break; + } + } + + negXInc(); + } + } +} + +/** + * Decompression Function - Move pDest ptr to next value to uncompress + * @remarks Originally called 'increments' + */ +void GfxSurface::nextDecompPtr(byte *&pDest) { + pDest += _xInc + _yInc; +} + +/** + * Decompression Function - set xInc to its opposite value + * @remarks Originally called 'NIH' + */ +void GfxSurface::negXInc() { + _xInc = -_xInc; +} + +/** + * Decompression Function - set yInc to its opposite value + * @remarks Originally called 'NIV' + */ +void GfxSurface::negYInc() { + _yInc = -_yInc; +} + +bool GfxSurface::TFP(int v) { + int diff = _yEnd - v; + if (!diff) + // Time to finish loop in outer method + return true; + + if (diff < (_thickness + 1)) + _thickness = diff - 1; + + return false; +} + +void GfxSurface::TF1(byte *&pDest, int &v) { + v += _thickness + 1; + pDest += (_thickness + 1) * _yInc; +} + +void GfxSurface::TF2(const byte *&pSrc, byte *&pDest, const byte *&pLookup, int &v) { + v += _thickness + 1; + + for (int idx = 0; idx <= _thickness; ++idx) { + *pDest = nextByte(pSrc, pLookup); + pDest += _yInc; + } +} + +/*-------------------------------------------------------------------------*/ + +GfxSurface::~GfxSurface() { + free(); +} + +/*-------------------------------------------------------------------------* + * Screen surface + *-------------------------------------------------------------------------*/ + +/** + * Called to populate the font data from the passed file + */ +void ScreenSurface::readFontData(Common::File &f, int dataSize) { + assert(dataSize == (FONT_NUM_CHARS * FONT_HEIGHT)); + f.read(_fontData, FONT_NUM_CHARS * FONT_HEIGHT); +} + +/** + * Returns a graphics surface representing a subset of the screen. The affected area + * is also marked as dirty + */ +Graphics::Surface ScreenSurface::lockArea(const Common::Rect &bounds) { + _dirtyRects.push_back(bounds); + + Graphics::Surface s; + s.format = format; + s.pixels = getBasePtr(bounds.left, bounds.top); + s.pitch = pitch; + s.w = bounds.width(); + s.h = bounds.height(); + + return s; +} + +/** + * Updates the affected areas of the surface to the underlying physical screen + */ +void ScreenSurface::updateScreen() { + // Iterate through copying dirty areas to the screen + for (Common::List<Common::Rect>::iterator i = _dirtyRects.begin(); i != _dirtyRects.end(); ++i) { + Common::Rect r = *i; + g_system->copyRectToScreen((const byte *)getBasePtr(r.left, r.top), pitch, + r.left, r.top, r.width(), r.height()); + } + _dirtyRects.clear(); + + // Update the screen + g_system->updateScreen(); +} + +/** + * Draws a decoded picture on the screen + * @remarks - Because the ScummVM surface is using a double height 640x400 surface to + * simulate the original 640x400 surface, all Y values have to be doubled. + * - Image resources are stored at 320x200, so when drawn onto the screen a single pixel + * from the source image is drawn using the two pixels at the given index in the palette map + * - Because the original game supported 320 width resolutions, the X coordinate + * also needs to be doubled for EGA mode + */ +void ScreenSurface::drawPicture(GfxSurface &surface, int x, int y) { + // Adjust the draw position by the draw offset + x += surface._offset.x; + y += surface._offset.y; + + // Lock the affected area of the surface to write to + Graphics::Surface destSurface = lockArea(Common::Rect(x * 2, y * 2, + (x + surface.w) * 2, (y + surface.h) * 2)); + + // Get a lookup for the palette mapping + const byte *paletteMap = &_vm->_curPict[2]; + + // Loop through writing + for (int yp = 0; yp < surface.h; ++yp) { + if (((y + yp) < 0) || ((y + yp) >= 200)) + continue; + + const byte *pSrc = (const byte *)surface.getBasePtr(0, yp); + byte *pDest = (byte *)destSurface.getBasePtr(0, yp * 2); + + for (int xp = 0; xp < surface.w; ++xp, ++pSrc) { + if (*pSrc == surface._transparency) { + // Transparent point, so skip pixels + pDest += 2; + } else { + // Draw the pixel using the specified index in the palette map + *pDest = paletteMap[*pSrc * 2]; + *(pDest + SCREEN_WIDTH) = paletteMap[*pSrc * 2]; + ++pDest; + + // Use the secondary mapping value to draw the secondary column pixel + *pDest = paletteMap[*pSrc * 2 + 1]; + *(pDest + SCREEN_WIDTH) = paletteMap[*pSrc * 2 + 1]; + ++pDest; + } + } + } +} + +/** + * Copys a given surface to the given position + */ +void ScreenSurface::copyFrom(Graphics::Surface &src, int x, int y) { + Graphics::Surface destSurface = lockArea(Common::Rect(x, y, x + src.w, y + src.h)); + + // Loop through writing + for (int yp = 0; yp < src.h; ++yp) { + if (((y + yp) < 0) || ((y + yp) >= SCREEN_HEIGHT)) + continue; + + const byte *pSrc = (const byte *)src.getBasePtr(0, yp); + byte *pDest = (byte *)getBasePtr(0, yp); + Common::copy(pSrc, pSrc + src.w, pDest); + } +} + +/** + * Draws a character at the specified co-ordinates + * @remarks Because the ScummVM surface is using a double height 640x400 surface to + * simulate the original 640x400 surface, all Y values have to be doubled + */ +void ScreenSurface::writeCharacter(const Common::Point &pt, unsigned char ch, int palIndex) { + Graphics::Surface destSurface = lockArea(Common::Rect(pt.x, pt.y * 2, + pt.x + FONT_WIDTH, (pt.y + FONT_HEIGHT) * 2)); + + // Get the start of the character to use + assert((ch >= ' ') && (ch <= (unsigned char)(32 + FONT_NUM_CHARS))); + const byte *charData = &_fontData[((int)ch - 32) * FONT_HEIGHT]; + + // Loop through decoding each character's data + for (int yp = 0; yp < FONT_HEIGHT; ++yp) { + byte *lineP = (byte *)destSurface.getBasePtr(0, yp * 2); + byte byteVal = *charData++; + + for (int xp = 0; xp < FONT_WIDTH; ++xp, ++lineP, byteVal <<= 1) { + if (byteVal & 0x80) { + *lineP = palIndex; + *(lineP + SCREEN_WIDTH) = palIndex; + } + } + } +} + +/** + * Draws a box at the specified position and size + * @remarks Because the ScummVM surface is using a double height 640x400 surface to + * simulate the original 640x400 surface, all Y values have to be doubled + */ +void ScreenSurface::drawBox(int x, int y, int dx, int dy, int col) { + if (_vm->_resolutionScaler == 1) { + x = (uint)x >> 1; + dx = (uint)dx >> 1; + } + + Graphics::Surface destSurface = lockArea(Common::Rect(x, y * 2, x + dx, (y + dy) * 2)); + + destSurface.hLine(0, 0, dx, col); + destSurface.hLine(0, 1, dx, col); + destSurface.hLine(0, destSurface.h - 1, dx, col); + destSurface.hLine(0, destSurface.h - 2, dx, col); + destSurface.vLine(0, 2, destSurface.h - 3, col); + destSurface.vLine(1, 2, destSurface.h - 3, col); + destSurface.vLine(dx - 1, 2, destSurface.h - 3, col); + destSurface.vLine(dx - 2, 2, destSurface.h - 3, col); +} + +/** + * Fills an area with the specified color + * @remarks Because the ScummVM surface is using a double height 640x400 surface to + * simulate the original 640x400 surface, all Y values have to be doubled + */ +void ScreenSurface::fillRect(int color, const Common::Rect &bounds) { + Graphics::Surface destSurface = lockArea(Common::Rect(bounds.left, bounds.top * 2, + bounds.right, bounds.bottom * 2)); + + // Fill the area + destSurface.fillRect(Common::Rect(0, 0, destSurface.w, destSurface.h), color); +} + +/** + * Clears the screen + */ +void ScreenSurface::clearScreen() { + Graphics::Surface destSurface = lockArea(Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)); + destSurface.fillRect(Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), 0); +} + +/** + * Sets a single pixel at the specified co-ordinates + * @remarks Because the ScummVM surface is using a double height 640x400 surface to + * simulate the original 640x400 surface, all Y values have to be doubled + */ +void ScreenSurface::setPixel(const Common::Point &pt, int palIndex) { + assert((pt.x >= 0) && (pt.y >= 0) && (pt.x <= SCREEN_WIDTH) && (pt.y <= SCREEN_ORIG_HEIGHT)); + Graphics::Surface destSurface = lockArea(Common::Rect(pt.x, pt.y * 2, pt.x + 1, (pt.y + 1) * 2)); + + byte *destP = (byte *)destSurface.pixels; + *destP = palIndex; + *(destP + SCREEN_WIDTH) = palIndex; +} + +/** + * Write out a string + * @remarks Originally called 'writeg' + */ +void ScreenSurface::drawString(const Common::String &l, int command) { + if (l == "") + return; + + _vm->_mouse.hideMouse(); + Common::Point pt = _textPos; + + int charWidth; + if (_vm->_resolutionScaler == 2) + charWidth = 6; + else + charWidth = 10; + + int x = pt.x + charWidth * l.size(); + int color = 0; + + switch (command) { + case 0: + case 2: + color = 15; + _vm->_screenSurface.fillRect(0, Common::Rect(pt.x, pt.y, x, pt.y + 7)); + break; + case 1: + case 3: + _vm->_screenSurface.fillRect(15, Common::Rect(pt.x, pt.y, x, pt.y + 7)); + break; + case 5: + color = 15; + break; + default: + // Default: Color set to zero (already done) + break; + } + + pt.x += 1; + pt.y += 1; + for (x = 1; (x <= (int)l.size()) && (l[x - 1] != 0); ++x) { + _vm->_screenSurface.writeCharacter(Common::Point(pt.x, pt.y), l[x - 1], color); + pt.x += charWidth; + } + _vm->_mouse.showMouse(); +} + +/** + * Gets the width in pixels of the specified string + */ +int ScreenSurface::getStringWidth(const Common::String &s) { + int charWidth = (_vm->_resolutionScaler == 2) ? 6 : 10; + + return s.size() * charWidth; +} + +void ScreenSurface::drawLine(int x, int y, int xx, int yy, int coul) { + int step, i; + float a, b; + float xr, yr, xro, yro; + + xr = x; + yr = y; + xro = xx; + yro = yy; + + if (abs(y - yy) > abs(x - xx)) { + a = (float)((x - xx)) / (y - yy); + b = (yr * xro - yro * xr) / (y - yy); + i = y; + if (y > yy) + step = -1; + else + step = 1; + do { + _vm->_screenSurface.setPixel(Common::Point(abs((int)(a * i + b)), i), coul); + i += step; + } while (i != yy); + } else { + a = (float)((y - yy)) / (x - xx); + b = ((yro * xr) - (yr * xro)) / (x - xx); + i = x; + if (x > xx) + step = -1; + else + step = 1; + do { + _vm->_screenSurface.setPixel(Common::Point(i, abs((int)(a * i + b))), coul); + i = i + step; + } while (i != xx); + } +} + +/** + * Draw plain rectangle + * @remarks Originally called 'paint_rect' + */ +void ScreenSurface::drawRectangle(int x, int y, int dx, int dy) { + int co; + + if (_vm->_currGraphicalDevice == MODE_CGA) + co = 3; + else + co = 11; + _vm->_screenSurface.fillRect(co, Common::Rect(x, y, x + dx, y + dy)); +} + +void ScreenSurface::setParent(MortevielleEngine *vm) { + _vm = vm; +} + + +} // End of namespace Mortevielle diff --git a/engines/mortevielle/graphics.h b/engines/mortevielle/graphics.h new file mode 100644 index 0000000000..e31f5da29a --- /dev/null +++ b/engines/mortevielle/graphics.h @@ -0,0 +1,117 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This code is based on original Mortville Manor DOS source code + * Copyright (c) 1987-1989 Lankhor + */ + +#ifndef MORTEVIELLE_GRAPHICS_H +#define MORTEVIELLE_GRAPHICS_H + +#include "common/file.h" +#include "common/list.h" +#include "common/rect.h" +#include "graphics/surface.h" + +namespace Mortevielle { +class MortevielleEngine; + +class PaletteManager { +private: + void setPalette(const int *palette, uint idx, uint size); + +public: + void setDefaultPalette(); +}; + +#define FONT_WIDTH 8 +#define FONT_HEIGHT 6 +#define FONT_NUM_CHARS 121 + +class GfxSurface : public Graphics::Surface { +private: + int _xp, _yp; + int _xSize, _ySize; + int _lookupIndex, _lookupValue; + bool _nibbleFlag; + int _thickness; + int _yInc, _yEnd, _xInc, _xEnd; + + byte nextNibble(const byte *&pSrc); + byte nextByte(const byte *&pSrc, const byte *&pLookup); + + void majTtxTty(); + int desanalyse(const byte *&pSrc); + void horizontal(const byte *&pSrc, byte *&pDest, const byte *&pLookup); + void vertical(const byte *&pSrc, byte *&pDest, const byte *&pLookup); + void decom11(const byte *&pSrc, byte *&pDest, const byte *&pLookup); + void diag(const byte *&pSrc, byte *&pDest, const byte *&pLookup); + void nextDecompPtr(byte *&pDest); + void negXInc(); + void negYInc(); + bool TFP(int v); + void TF1(byte *&pDest, int &v); + void TF2(const byte *&pSrc, byte *&pDest, const byte *&pLookup, int &v); +public: + // Specifies offset when drawing the image + Common::Point _offset; + // Transparency palette index + int _transparency; + + ~GfxSurface(); + + void decode(const byte *pSrc); +}; + +class ScreenSurface: public Graphics::Surface { +private: + MortevielleEngine *_vm; + + Common::List<Common::Rect> _dirtyRects; + byte _fontData[FONT_NUM_CHARS * FONT_HEIGHT]; + +public: + Common::Point _textPos; // Original called xwhere/ywhere + void readFontData(Common::File &f, int dataSize); + Graphics::Surface lockArea(const Common::Rect &bounds); + void updateScreen(); + void drawPicture(GfxSurface &surface, int x, int y); + void copyFrom(Graphics::Surface &src, int x, int y); + void writeCharacter(const Common::Point &pt, unsigned char ch, int palIndex); + void drawBox(int x, int y, int dx, int dy, int col); + void fillRect(int color, const Common::Rect &bounds); + void clearScreen(); + void putxy(int x, int y) { _textPos = Common::Point(x, y); } + void drawString(const Common::String &l, int command); + int getStringWidth(const Common::String &s); + void drawLine(int x, int y, int xx, int yy, int coul); + void drawRectangle(int x, int y, int dx, int dy); + void setParent(MortevielleEngine *vm); + + // TODO: Refactor code to remove this method, for increased performance + void setPixel(const Common::Point &pt, int palIndex); +}; + +} // End of namespace Mortevielle + +#endif diff --git a/engines/mortevielle/menu.cpp b/engines/mortevielle/menu.cpp new file mode 100644 index 0000000000..f86fd208c1 --- /dev/null +++ b/engines/mortevielle/menu.cpp @@ -0,0 +1,599 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file _distributed with this source _distribution. + * + * This program is free software; you can re_distribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is _distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This code is based on original Mortville Manor DOS source code + * Copyright (c) 1987-1989 Lankhor + */ + +#include "mortevielle/mortevielle.h" + +#include "mortevielle/menu.h" +#include "mortevielle/mouse.h" +#include "mortevielle/outtext.h" + +#include "common/scummsys.h" +#include "common/str.h" +#include "common/textconsole.h" + +namespace Mortevielle { + +const byte menuConstants[8][4] = { + { 7, 37, 23, 8}, + {19, 33, 23, 7}, + {31, 89, 10, 21}, + {43, 25, 11, 5}, + {55, 37, 5, 8}, + {64, 13, 11, 2}, + {62, 42, 13, 9}, + {62, 46, 13, 10} +}; + +/** + * Setup a menu's contents + * @remarks Originally called 'menut' + */ +void Menu::setText(int menuId, int actionId, Common::String name) { + Common::String s = name; + + while (s.size() < 22) + s += ' '; + + switch (menuId) { + case MENU_INVENTORY: + if (actionId != 7) { + _inventoryStringArray[actionId] = s; + _inventoryStringArray[actionId].insertChar(' ', 0); + } + break; + case MENU_MOVE: + _moveStringArray[actionId] = s; + break; + case MENU_ACTION: + _actionStringArray[actionId] = s; + break; + case MENU_SELF: + _selfStringArray[actionId] = s; + break; + case MENU_DISCUSS: + _discussStringArray[actionId] = s; + break; + default: + break; + } +} + +/** + * Init destination menu + * @remarks Originally called 'tmlieu' + */ +void Menu::setDestinationText(int roomId) { + Common::String nomp; + + if (roomId == 26) + roomId = LANDING; + + int destinationId = 0; + for (; (destinationId < 7) && (_vm->_destinationArray[destinationId][roomId]); ++destinationId) { + nomp = _vm->getString(_vm->_destinationArray[destinationId][roomId] + kMenuPlaceStringIndex); + while (nomp.size() < 20) + nomp += ' '; + setText(_moveMenu[destinationId + 1]._menuId, _moveMenu[destinationId + 1]._actionId, nomp); + } + nomp = "* "; + for (int i = 7; i >= destinationId + 1; --i) + setText(_moveMenu[i]._menuId, _moveMenu[i]._actionId, nomp); +} + +/** + * _disable a menu item + * @param menuId Menu number + * @param actionId Item index + */ +void Menu::disableMenuItem(int menuId, int actionId) { + switch (menuId) { + case MENU_INVENTORY: + if (actionId > 6) { + _inventoryStringArray[actionId].setChar('<', 0); + _inventoryStringArray[actionId].setChar('>', 21); + } else + _inventoryStringArray[actionId].setChar('*', 0); + break; + case MENU_MOVE: + _moveStringArray[actionId].setChar('*', 0); + break; + case MENU_ACTION: + _actionStringArray[actionId].setChar('*', 0); + break; + case MENU_SELF: + _selfStringArray[actionId].setChar('*', 0); + break; + case MENU_DISCUSS: + _discussStringArray[actionId].setChar('*', 0); + break; + default: + break; + } +} + +/** + * Enable a menu item + * @param menuId Menu number + * @param actionId Item index + * @remarks Originally called menu_enable + */ +void Menu::enableMenuItem(int menuId, int actionId) { + switch (menuId) { + case MENU_INVENTORY: + _inventoryStringArray[actionId].setChar(' ', 0); + _inventoryStringArray[actionId].setChar(' ', 21); + break; + case MENU_MOVE: + _moveStringArray[actionId].setChar(' ', 0); + break; + case MENU_ACTION: + _actionStringArray[actionId].setChar(' ', 0); + break; + case MENU_SELF: + _selfStringArray[actionId].setChar(' ', 0); + // The original sets two times the same value. Skipped + // _selfStringArray[l].setChar(' ', 0); + break; + case MENU_DISCUSS: + _discussStringArray[actionId].setChar(' ', 0); + break; + default: + break; + } +} + +void Menu::displayMenu() { + int ind_tabl, k, col; + + int pt, x, y, color, msk, num_letr; + + _vm->_mouse.hideMouse(); + + _vm->_screenSurface.fillRect(7, Common::Rect(0, 0, 639, 10)); + col = 28 * _vm->_resolutionScaler; + if (_vm->_currGraphicalDevice == MODE_CGA) + color = 1; + else + color = 9; + num_letr = 0; + do { // One character after the other + ++num_letr; + ind_tabl = 0; + y = 1; + do { // One column after the other + k = 0; + x = col; + do { // One line after the other + msk = 0x80; + for (pt = 0; pt <= 7; ++pt) { + if ((_charArr[num_letr - 1][ind_tabl] & msk) != 0) { + _vm->_screenSurface.setPixel(Common::Point(x + 1, y + 1), 0); + _vm->_screenSurface.setPixel(Common::Point(x, y + 1), 0); + _vm->_screenSurface.setPixel(Common::Point(x, y), color); + } + msk = (uint)msk >> 1; + ++x; + } + ++ind_tabl; + ++k; + } while (k != 3); + ++y; + } while (y != 9); + col += 48 * _vm->_resolutionScaler; + } while (num_letr != 6); + _vm->_mouse.showMouse(); +} + +/** + * Show the menu + */ +void Menu::drawMenu() { + displayMenu(); + _menuActive = true; + _msg4 = OPCODE_NONE; + _msg3 = OPCODE_NONE; + _menuSelected = false; + _vm->setMouseClick(false); + _multiTitle = false; +} + +/** + * Menu function - Invert a menu entry + * @remarks Originally called 'invers' + */ +void Menu::invert(int indx) { + if (_msg4 == OPCODE_NONE) + return; + + int menuIndex = _msg4 & 0xFF; + + _vm->_screenSurface.putxy(menuConstants[_msg3 - 1][0] << 3, (menuIndex + 1) << 3); + + Common::String str; + switch (_msg3) { + case MENU_INVENTORY: + str = _inventoryStringArray[menuIndex]; + break; + case MENU_MOVE: + str = _moveStringArray[menuIndex]; + break; + case MENU_ACTION: + str = _actionStringArray[menuIndex]; + break; + case MENU_SELF: + str = _selfStringArray[menuIndex]; + break; + case MENU_DISCUSS: + str = _discussStringArray[menuIndex]; + break; + case MENU_FILE: + str = _vm->getEngineString(S_SAVE_LOAD + menuIndex); + break; + case MENU_SAVE: + str = _vm->getEngineString(S_SAVE_LOAD + 1); + str += ' '; + str += (char)(48 + menuIndex); + break; + case MENU_LOAD: + if (menuIndex == 1) { + str = _vm->getEngineString(S_RESTART); + } else { + str = _vm->getEngineString(S_SAVE_LOAD + 2); + str += ' '; + str += (char)(47 + menuIndex); + } + break; + default: + break; + } + if ((str[0] != '*') && (str[0] != '<')) + _vm->_screenSurface.drawString(str, indx); + else + _msg4 = OPCODE_NONE; +} + +void Menu::util(Common::Point pos) { + + int ymx = (menuConstants[_msg3 - 1][3] << 3) + 16; + int dxcar = menuConstants[_msg3 - 1][2]; + int xmn = (menuConstants[_msg3 - 1][0] << 2) * _vm->_resolutionScaler; + + int ix; + if (_vm->_resolutionScaler == 1) + ix = 5; + else + ix = 3; + int xmx = dxcar * ix * _vm->_resolutionScaler + xmn + 2; + if ((pos.x > xmn) && (pos.x < xmx) && (pos.y < ymx) && (pos.y > 15)) { + ix = (((uint)pos.y >> 3) - 1) + (_msg3 << 8); + if (ix != _msg4) { + invert(1); + _msg4 = ix; + invert(0); + } + } else if (_msg4 != OPCODE_NONE) { + invert(1); + _msg4 = OPCODE_NONE; + } +} + +/** + * Draw a menu + */ +void Menu::menuDown(int ii) { + int cx, xcc, xco; + int lignNumb; + + // Make a copy of the current screen surface for later restore + _vm->_backgroundSurface.copyFrom(_vm->_screenSurface); + + // Draw the menu + xco = menuConstants[ii - 1][0]; + lignNumb = menuConstants[ii - 1][3]; + _vm->_mouse.hideMouse(); + xco = xco << 3; + if (_vm->_resolutionScaler == 1) + cx = 10; + else + cx = 6; + xcc = xco + (menuConstants[ii - 1][2] * cx) + 6; + if ((ii == 4) && (_vm->getLanguage() == Common::EN_ANY)) + // Extra width needed for Self menu in English version + xcc = 435; + + _vm->_screenSurface.fillRect(15, Common::Rect(xco, 12, xcc, 10 + (menuConstants[ii - 1][1] << 1))); + _vm->_screenSurface.fillRect(0, Common::Rect(xcc, 12, xcc + 4, 10 + (menuConstants[ii - 1][1] << 1))); + _vm->_screenSurface.fillRect(0, Common::Rect(xco, 8 + (menuConstants[ii - 1][1] << 1), xcc + 4, 12 + (menuConstants[ii - 1][1] << 1))); + _vm->_screenSurface.putxy(xco, 16); + cx = 0; + do { + ++cx; + switch (ii) { + case 1: + if (_inventoryStringArray[cx][0] != '*') + _vm->_screenSurface.drawString(_inventoryStringArray[cx], 4); + break; + case 2: + if (_moveStringArray[cx][0] != '*') + _vm->_screenSurface.drawString(_moveStringArray[cx], 4); + break; + case 3: + if (_actionStringArray[cx][0] != '*') + _vm->_screenSurface.drawString(_actionStringArray[cx], 4); + break; + case 4: + if (_selfStringArray[cx][0] != '*') + _vm->_screenSurface.drawString(_selfStringArray[cx], 4); + break; + case 5: + if (_discussStringArray[cx][0] != '*') + _vm->_screenSurface.drawString(_discussStringArray[cx], 4); + break; + case 6: + _vm->_screenSurface.drawString(_vm->getEngineString(S_SAVE_LOAD + cx), 4); + break; + case 7: { + Common::String s = _vm->getEngineString(S_SAVE_LOAD + 1); + s += ' '; + s += (char)(48 + cx); + _vm->_screenSurface.drawString(s, 4); + } + break; + case 8: + if (cx == 1) + _vm->_screenSurface.drawString(_vm->getEngineString(S_RESTART), 4); + else { + Common::String s = _vm->getEngineString(S_SAVE_LOAD + 2); + s += ' '; + s += (char)(47 + cx); + _vm->_screenSurface.drawString(s, 4); + } + break; + default: + break; + } + _vm->_screenSurface.putxy(xco, _vm->_screenSurface._textPos.y + 8); + } while (cx != lignNumb); + _multiTitle = true; + _vm->_mouse.showMouse(); +} + +/** + * Menu is being removed, so restore the previous background area. + */ +void Menu::menuUp(int msgId) { + if (_multiTitle) { + /* Restore the background area */ + assert(_vm->_screenSurface.pitch == _vm->_backgroundSurface.pitch); + + // Get a pointer to the source and destination of the area to restore + const byte *pSrc = (const byte *)_vm->_backgroundSurface.getBasePtr(0, 10); + Graphics::Surface destArea = _vm->_screenSurface.lockArea(Common::Rect(0, 10, SCREEN_WIDTH, SCREEN_HEIGHT)); + byte *pDest = (byte *)destArea.getBasePtr(0, 0); + + // Copy the data + Common::copy(pSrc, pSrc + (400 - 10) * SCREEN_WIDTH, pDest); + + _multiTitle = false; + } +} + +/** + * Erase the menu + */ +void Menu::eraseMenu() { + _menuActive = false; + _vm->setMouseClick(false); + menuUp(_msg3); +} + +/** + * Handle updates to the menu + * @remarks Originally called 'mdn' + */ +void Menu::updateMenu() { + if (!_menuActive) + return; + + Common::Point curPos = _vm->_mouse._pos; + if (!_vm->getMouseClick()) { + if (curPos == _vm->_prevPos) + return; + else + _vm->_prevPos = curPos; + + bool tes = (curPos.y < 11) + && ((curPos.x >= (28 * _vm->_resolutionScaler) && curPos.x <= (28 * _vm->_resolutionScaler + 24)) + || (curPos.x >= (76 * _vm->_resolutionScaler) && curPos.x <= (76 * _vm->_resolutionScaler + 24)) + || ((curPos.x > 124 * _vm->_resolutionScaler) && (curPos.x < 124 * _vm->_resolutionScaler + 24)) + || ((curPos.x > 172 * _vm->_resolutionScaler) && (curPos.x < 172 * _vm->_resolutionScaler + 24)) + || ((curPos.x > 220 * _vm->_resolutionScaler) && (curPos.x < 220 * _vm->_resolutionScaler + 24)) + || ((curPos.x > 268 * _vm->_resolutionScaler) && (curPos.x < 268 * _vm->_resolutionScaler + 24))); + if (tes) { + int ix; + + if (curPos.x < 76 * _vm->_resolutionScaler) + ix = MENU_INVENTORY; + else if (curPos.x < 124 * _vm->_resolutionScaler) + ix = MENU_MOVE; + else if (curPos.x < 172 * _vm->_resolutionScaler) + ix = MENU_ACTION; + else if (curPos.x < 220 * _vm->_resolutionScaler) + ix = MENU_SELF; + else if (curPos.x < 268 * _vm->_resolutionScaler) + ix = MENU_DISCUSS; + else + ix = MENU_FILE; + + if ((ix != _msg3) || (!_multiTitle)) + if (!((ix == MENU_FILE) && ((_msg3 == MENU_SAVE) || (_msg3 == MENU_LOAD)))) { + menuUp(_msg3); + menuDown(ix); + _msg3 = ix; + _msg4 = OPCODE_NONE; + } + } else { // Not in the MenuTitle line + if ((curPos.y > 11) && (_multiTitle)) + util(curPos); + } + } else { // There was a click + if ((_msg3 == MENU_FILE) && (_msg4 != OPCODE_NONE)) { + // Another menu to be _displayed + _vm->setMouseClick(false); + menuUp(_msg3); + if ((_msg4 & 0xFF) == 1) + _msg3 = MENU_SAVE; + else + _msg3 = MENU_LOAD; + menuDown(_msg3); + + _vm->setMouseClick(false); + } else { + // A menu was clicked on + _menuSelected = (_multiTitle) && (_msg4 != OPCODE_NONE); + menuUp(_msg3); + _vm->_currAction = _msg4; + _vm->_currMenu = _msg3; + _msg3 = OPCODE_NONE; + _msg4 = OPCODE_NONE; + + _vm->setMouseClick(false); + } + } +} + +void Menu::initMenu(MortevielleEngine *vm) { + _vm = vm; + + int i; + Common::File f; + + if (!f.open("menufr.mor")) + if (!f.open("menual.mor")) + if (!f.open("menu.mor")) + error("Missing file - menufr.mor or menual.mor or menu.mor"); + + f.read(_charArr, 7 * 24); + f.close(); + + // Skipped: dialog asking to swap floppy + + for (i = 1; i <= 8; ++i) + _inventoryStringArray[i] = "* "; + _inventoryStringArray[7] = "< -*-*-*-*-*-*-*-*-*- "; + for (i = 1; i <= 7; ++i) + _moveStringArray[i] = "* "; + i = 1; + do { + _actionStringArray[i] = _vm->getString(i + kMenuActionStringIndex); + + while (_actionStringArray[i].size() < 10) + _actionStringArray[i] += ' '; + + if (i < 9) { + if (i < 6) { + _selfStringArray[i] = _vm->getString(i + kMenuSelfStringIndex); + while (_selfStringArray[i].size() < 10) + _selfStringArray[i] += ' '; + } + _discussStringArray[i] = _vm->getString(i + kMenuSayStringIndex) + ' '; + } + ++i; + } while (i != 22); + for (i = 1; i <= 8; ++i) { + _discussMenu[i]._menuId = MENU_DISCUSS; + _discussMenu[i]._actionId = i; + if (i < 8) { + _moveMenu[i]._menuId = MENU_MOVE; + _moveMenu[i]._actionId = i; + } + _inventoryMenu[i]._menuId = MENU_INVENTORY; + _inventoryMenu[i]._actionId = i; + if (i > 6) + disableMenuItem(_inventoryMenu[i]._menuId, _inventoryMenu[i]._actionId); + } + _msg3 = OPCODE_NONE; + _msg4 = OPCODE_NONE; + _vm->_currMenu = OPCODE_NONE; + _vm->_currAction = OPCODE_NONE; + _vm->setMouseClick(false); +} + +/** + * Engine function - Switch action menu to "Search" mode + * @remarks Originally called 'mfoudi' + */ +void Menu::setSearchMenu() { + for (int i = 1; i <= 7; ++i) + disableMenuItem(MENU_MOVE, _moveMenu[i]._actionId); + + for (int i = 1; i <= 11; ++i) + disableMenuItem(_actionMenu[i]._menuId, _actionMenu[i]._actionId); + + setText(OPCODE_SOUND >> 8, OPCODE_SOUND & 0xFF, _vm->getEngineString(S_SUITE)); + setText(OPCODE_LIFT >> 8, OPCODE_LIFT & 0xFF, _vm->getEngineString(S_STOP)); +} + +/** + * Engine function - Switch action menu from "Search" mode back to normal mode + * @remarks Originally called 'mfouen' + */ +void Menu::unsetSearchMenu() { + setDestinationText(_vm->_coreVar._currPlace); + for (int i = 1; i <= 11; ++i) + enableMenuItem(_actionMenu[i]._menuId, _actionMenu[i]._actionId); + + setText(OPCODE_SOUND >> 8, OPCODE_SOUND & 0xFF, _vm->getEngineString(S_PROBE)); + setText(OPCODE_LIFT >> 8, OPCODE_LIFT & 0xFF, _vm->getEngineString(S_RAISE)); +} + +/** + * Set Inventory menu texts + * @remarks Originally called 'modinv' + */ +void Menu::setInventoryText() { + Common::String nomp; + + int cy = 0; + for (int i = 1; i <= 6; ++i) { + if (_vm->_coreVar._inventory[i] != 0) { + ++cy; + int r = _vm->_coreVar._inventory[i] + 400; + nomp = _vm->getString(r - 501 + kInventoryStringIndex); + setText(_inventoryMenu[cy]._menuId, _inventoryMenu[cy]._actionId, nomp); + enableMenuItem(_inventoryMenu[i]._menuId, _inventoryMenu[i]._actionId); + } + } + + if (cy < 6) { + for (int i = cy + 1; i <= 6; ++i) { + setText(_inventoryMenu[i]._menuId, _inventoryMenu[i]._actionId, " "); + disableMenuItem(_inventoryMenu[i]._menuId, _inventoryMenu[i]._actionId); + } + } +} +} // End of namespace Mortevielle diff --git a/engines/mortevielle/menu.h b/engines/mortevielle/menu.h new file mode 100644 index 0000000000..2428d8917b --- /dev/null +++ b/engines/mortevielle/menu.h @@ -0,0 +1,112 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This code is based on original Mortville Manor DOS source code + * Copyright (c) 1987-1989 Lankhor + */ + +#ifndef MORTEVIELLE_MENU_H +#define MORTEVIELLE_MENU_H + +#include "common/rect.h" +#include "common/str.h" + +namespace Mortevielle { +class MortevielleEngine; + +enum { + MENU_NONE = 0, MENU_INVENTORY = 1, MENU_MOVE = 2, MENU_ACTION = 3, + MENU_SELF = 4, MENU_DISCUSS = 5, MENU_FILE = 6, MENU_SAVE = 7, + MENU_LOAD = 8 +}; + +enum verbs {OPCODE_NONE = 0, OPCODE_ATTACH = 0x301, OPCODE_WAIT = 0x302, OPCODE_FORCE = 0x303, OPCODE_SLEEP = 0x304, OPCODE_LISTEN = 0x305, +OPCODE_ENTER = 0x306, OPCODE_CLOSE = 0x307, OPCODE_SEARCH = 0x308, OPCODE_KNOCK = 0x309, OPCODE_SCRATCH = 0x30a, +OPCODE_READ = 0x30b, OPCODE_EAT = 0x30c, OPCODE_PLACE = 0x30d, OPCODE_OPEN = 0x30e, OPCODE_TAKE = 0x30f, +OPCODE_LOOK = 0x310, OPCODE_SMELL = 0x311, OPCODE_SOUND = 0x312, OPCODE_LEAVE = 0x313, OPCODE_LIFT = 0x314, +OPCODE_TURN = 0x315, OPCODE_SHIDE = 0x401, OPCODE_SSEARCH = 0x402, OPCODE_SREAD = 0x403, OPCODE_SPUT = 0x404, +OPCODE_SLOOK = 0x405}; + +struct menuItem { + int _menuId; + int _actionId; +}; + +static const menuItem _actionMenu[12] = { + {OPCODE_NONE >> 8, OPCODE_NONE & 0xFF}, + {OPCODE_SHIDE >> 8, OPCODE_SHIDE & 0xFF}, + {OPCODE_ATTACH >> 8, OPCODE_ATTACH & 0xFF}, + {OPCODE_FORCE >> 8, OPCODE_FORCE & 0xFF}, + {OPCODE_SLEEP >> 8, OPCODE_SLEEP & 0xFF}, + {OPCODE_ENTER >> 8, OPCODE_ENTER & 0xFF}, + {OPCODE_CLOSE >> 8, OPCODE_CLOSE & 0xFF}, + {OPCODE_KNOCK >> 8, OPCODE_KNOCK & 0xFF}, + {OPCODE_EAT >> 8, OPCODE_EAT & 0xFF}, + {OPCODE_PLACE >> 8, OPCODE_PLACE & 0xFF}, + {OPCODE_OPEN >> 8, OPCODE_OPEN & 0xFF}, + {OPCODE_LEAVE >> 8, OPCODE_LEAVE & 0xFF} +}; + +class Menu { +private: + MortevielleEngine *_vm; + + byte _charArr[7][24]; + int _msg3; + int _msg4; + + void util(Common::Point pos); + void invert(int indx); + void menuDown(int ii); +public: + bool _menuActive; + bool _menuSelected; + bool _multiTitle; + bool _menuDisplayed; + Common::String _inventoryStringArray[9]; + Common::String _moveStringArray[8]; + Common::String _actionStringArray[22]; + Common::String _selfStringArray[7]; + Common::String _discussStringArray[9]; + menuItem _discussMenu[9]; + menuItem _inventoryMenu[9]; + menuItem _moveMenu[8]; + + void setText(int menuId, int actionId, Common::String name); + void setDestinationText(int roomId); + void setInventoryText(); + void disableMenuItem(int menuId, int actionId); + void enableMenuItem(int menuId, int actionId); + void displayMenu(); + void drawMenu(); + void menuUp(int msgId); + void eraseMenu(); + void updateMenu(); + void initMenu(MortevielleEngine *vm); + + void setSearchMenu(); + void unsetSearchMenu(); +}; + +} // End of namespace Mortevielle +#endif diff --git a/engines/mortevielle/module.mk b/engines/mortevielle/module.mk new file mode 100644 index 0000000000..e18657cb6a --- /dev/null +++ b/engines/mortevielle/module.mk @@ -0,0 +1,24 @@ +MODULE := engines/mortevielle + +MODULE_OBJS := \ + actions.o \ + debugger.o \ + detection.o \ + dialogs.o \ + graphics.o \ + menu.o \ + mortevielle.o \ + mouse.o \ + outtext.o \ + saveload.o \ + sound.o \ + speech.o \ + utils.o + +# This module can be built as a plugin +ifeq ($(ENABLE_MORTEVIELLE), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/mortevielle/mortevielle.cpp b/engines/mortevielle/mortevielle.cpp new file mode 100644 index 0000000000..b4b46a4286 --- /dev/null +++ b/engines/mortevielle/mortevielle.cpp @@ -0,0 +1,445 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This code is based on original Mortville Manor DOS source code + * Copyright (c) 1987-1989 Lankhor + */ + +#include "mortevielle/mortevielle.h" + +#include "mortevielle/dialogs.h" +#include "mortevielle/menu.h" +#include "mortevielle/mouse.h" +#include "mortevielle/outtext.h" +#include "mortevielle/saveload.h" +#include "mortevielle/outtext.h" + +#include "common/system.h" +#include "common/config-manager.h" +#include "common/debug-channels.h" +#include "engines/util.h" +#include "engines/engine.h" +#include "graphics/palette.h" +#include "graphics/pixelformat.h" + +namespace Mortevielle { + +MortevielleEngine *g_vm; + +MortevielleEngine::MortevielleEngine(OSystem *system, const ADGameDescription *gameDesc): + Engine(system), _gameDescription(gameDesc), _randomSource("mortevielle"), + _soundManager(_mixer) { + g_vm = this; + _debugger.setParent(this); + _dialogManager.setParent(this); + _screenSurface.setParent(this); + _mouse.setParent(this); + _text.setParent(this); + _soundManager.setParent(this); + _speechManager.setParent(this); + _savegameManager.setParent(this); + + _lastGameFrame = 0; + _mouseClick = false; + _inMainGameLoop = false; + _quitGame = false; + + _roomPresenceLuc = false; + _roomPresenceIda = false; + _purpleRoomPresenceLeo = false; + _roomPresenceGuy = false; + _roomPresenceEva = false; + _roomPresenceMax = false; + _roomPresenceBob = false; + _roomPresencePat = false; + _toiletsPresenceBobMax = false; + _bathRoomPresenceBobMax = false; + _room9PresenceLeo = false; + + _soundOff = false; + _largestClearScreen = false; + _hiddenHero = false; + _heroSearching = false; + _keyPressedEsc = false; + _reloadCFIEC = false; + + _blo = false; + _col = false; + _syn = false; + _obpart = false; + _destinationOk = false; + _anyone = false; + _uptodatePresence = false; + + _textColor = 0; + _currGraphicalDevice = -1; + _newGraphicalDevice = -1; + _place = -1; + + _x26KeyCount = -1; + _caff = -1; + _day = 0; + + memset(_mem, 0, sizeof(_mem)); + _curPict = nullptr; + _curAnim = nullptr; + _rightFramePict = nullptr; + _compMusicBuf1 = nullptr; + _compMusicBuf2 = nullptr; +} + +MortevielleEngine::~MortevielleEngine() { + free(_curPict); + free(_curAnim); + free(_rightFramePict); + free(_compMusicBuf1); + free(_compMusicBuf2); +} + +/** + * Specifies whether the engine supports given features + */ +bool MortevielleEngine::hasFeature(EngineFeature f) const { + return + (f == kSupportsRTL) || + (f == kSupportsLoadingDuringRuntime) || + (f == kSupportsSavingDuringRuntime); +} + +/** + * Return true if a game can currently be loaded + */ +bool MortevielleEngine::canLoadGameStateCurrently() { + // Saving is only allowed in the main game event loop + return _inMainGameLoop; +} + +/** + * Return true if a game can currently be saved + */ +bool MortevielleEngine::canSaveGameStateCurrently() { + // Loading is only allowed in the main game event loop + return _inMainGameLoop; +} + +/** + * Load in a savegame at the specified slot number + */ +Common::Error MortevielleEngine::loadGameState(int slot) { + return _savegameManager.loadGame(slot); +} + +/** + * Save the current game + */ +Common::Error MortevielleEngine::saveGameState(int slot, const Common::String &desc) { + if (slot == 0) + return Common::kWritingFailed; + + return _savegameManager.saveGame(slot, desc); +} + +/** + * Support method that generates a savegame name + * @param slot Slot number + */ +Common::String MortevielleEngine::generateSaveFilename(const Common::String &target, int slot) { + if (slot == 0) + // Initial game state loaded when the game starts + return "sav0.mor"; + + return Common::String::format("%s.%03d", target.c_str(), slot); +} + +/** + * Initialize the game state + */ +Common::ErrorCode MortevielleEngine::initialize() { + // Initialize graphics mode + initGraphics(SCREEN_WIDTH, SCREEN_HEIGHT, true); + + // Set debug channels + DebugMan.addDebugChannel(kMortevielleCore, "core", "Core debugging"); + DebugMan.addDebugChannel(kMortevielleGraphics, "graphics", "Graphics debugging"); + + // Set up an intermediate screen surface + _screenSurface.create(SCREEN_WIDTH, SCREEN_HEIGHT, Graphics::PixelFormat::createFormatCLUT8()); + + // Set the screen mode + _currGraphicalDevice = MODE_EGA; + _resolutionScaler = 2; + + _txxFileFl = false; + // Load texts from TXX files + loadTexts(); + + // Load the mort.dat resource + Common::ErrorCode result = loadMortDat(); + if (result != Common::kNoError) { + _screenSurface.free(); + return result; + } + + // Load some error messages (was previously in chartex()) + _hintPctMessage = getString(580); // You should have noticed %d hints + + // Set default EGA palette + _paletteManager.setDefaultPalette(); + + // Setup the mouse cursor + initMouse(); + + _currGraphicalDevice = MODE_EGA; + _newGraphicalDevice = _currGraphicalDevice; + loadPalette(); + loadCFIPH(); + loadCFIEC(); + decodeNumber(&_cfiecBuffer[161 * 16], (_cfiecBufferSize - (161 * 16)) / 64); + _x26KeyCount = 1; + initMaxAnswer(); + initMouse(); + + loadPlaces(); + _soundOff = false; + _largestClearScreen = false; + + testKeyboard(); + showConfigScreen(); + _newGraphicalDevice = _currGraphicalDevice; + testKeyboard(); + if (_newGraphicalDevice != _currGraphicalDevice) + _currGraphicalDevice = _newGraphicalDevice; + hirs(); + + return Common::kNoError; +} + +/** + * Loads the contents of the mort.dat data file + */ +Common::ErrorCode MortevielleEngine::loadMortDat() { + Common::File f; + + // Open the mort.dat file + if (!f.open(MORT_DAT)) { + GUIErrorMessage("Could not locate 'mort.dat'."); + return Common::kReadingFailed; + } + + // Validate the data file header + char fileId[4]; + f.read(fileId, 4); + if (strncmp(fileId, "MORT", 4) != 0) { + GUIErrorMessage("The located mort.dat data file is invalid"); + return Common::kReadingFailed; + } + + // Check the version + if (f.readByte() < MORT_DAT_REQUIRED_VERSION) { + GUIErrorMessage("The located mort.dat data file is too old, please download an updated version on scummvm.org"); + return Common::kReadingFailed; + } + f.readByte(); // Minor version + + // Loop to load resources from the data file + while (f.pos() < f.size()) { + // Get the Id and size of the next resource + char dataType[4]; + int dataSize; + f.read(dataType, 4); + dataSize = f.readUint16LE(); + + if (!strncmp(dataType, "FONT", 4)) { + // Font resource + _screenSurface.readFontData(f, dataSize); + } else if (!strncmp(dataType, "SSTR", 4)) { + readStaticStrings(f, dataSize, kStaticStrings); + } else if ((!strncmp(dataType, "GSTR", 4)) && (!_txxFileFl)) { + readStaticStrings(f, dataSize, kGameStrings); + } else { + // Unknown section + f.skip(dataSize); + } + } + + // Close the file + f.close(); + + assert(_engineStrings.size() > 0); + return Common::kNoError; +} + +/** + * Read in a static strings block, and if the language matches, load up the static strings + */ +void MortevielleEngine::readStaticStrings(Common::File &f, int dataSize, DataType dataType) { + // Figure out what language Id is needed + byte desiredLanguageId; + switch(getLanguage()) { + case Common::EN_ANY: + desiredLanguageId = LANG_ENGLISH; + break; + case Common::FR_FRA: + desiredLanguageId = LANG_FRENCH; + break; + case Common::DE_DEU: + desiredLanguageId = LANG_GERMAN; + break; + default: + warning("Language not supported, switching to English"); + desiredLanguageId = LANG_ENGLISH; + break; + } + + // Read in the language + byte languageId = f.readByte(); + --dataSize; + + // If the language isn't correct, then skip the entire block + if (languageId != desiredLanguageId) { + f.skip(dataSize); + return; + } + + // Load in each of the strings + while (dataSize > 0) { + Common::String s; + char ch; + while ((ch = (char)f.readByte()) != '\0') + s += ch; + + if (dataType == kStaticStrings) + _engineStrings.push_back(s); + else if (dataType == kGameStrings) + _gameStrings.push_back(s); + + dataSize -= s.size() + 1; + } + assert(dataSize == 0); +} + +/*-------------------------------------------------------------------------*/ + +Common::Error MortevielleEngine::run() { + // Initialize the game + Common::ErrorCode err = initialize(); + if (err != Common::kNoError) + return err; + + // Check for a savegame + int loadSlot = 0; + if (ConfMan.hasKey("save_slot")) { + int gameToLoad = ConfMan.getInt("save_slot"); + if ((gameToLoad >= 1) && (gameToLoad <= 999)) + loadSlot = gameToLoad; + } + + if (loadSlot == 0) + // Show the game introduction + showIntroduction(); + + // Either load the initial game state savegame, or the specified savegame number + adzon(); + _savegameManager.loadSavegame(generateSaveFilename(loadSlot)); + + // Run the main game loop + mainGame(); + + // Cleanup (allocated in initialize()) + _screenSurface.free(); + free(_speechManager._cfiphBuffer); + free(_cfiecBuffer); + + return Common::kNoError; +} + +/** + * Show the game introduction + */ +void MortevielleEngine::showIntroduction() { + _dialogManager.displayIntroScreen(false); + _speechManager._mlec = 0; + _dialogManager.checkForF8(142, false); + if (shouldQuit()) + return; + + _dialogManager.displayIntroFrame2(); + _dialogManager.checkForF8(143, true); + if (shouldQuit()) + return; + + // TODO: Once music (Amiga/Atari ports) is implemented, only use the below delay if music is turned off + showTitleScreen(); + delay(3000); + music(); +} + +/** + * Main game loop. Handles potentially playing the game multiple times, such as if the player + * loses, and chooses to start playing the game again. + */ +void MortevielleEngine::mainGame() { + if (_reloadCFIEC) + loadCFIEC(); + + for (_crep = 1; _crep <= _x26KeyCount; ++_crep) + decodeNumber(&_cfiecBuffer[161 * 16], (_cfiecBufferSize - (161 * 16)) / 64); + + loadBRUIT5(); + _menu.initMenu(this); + + charToHour(); + initGame(); + hirs(); + drawRightFrame(); + _mouse.showMouse(); + + // Loop to play the game + do { + playGame(); + if (shouldQuit()) + return; + } while (!_quitGame); +} + +/** + * This method handles playing a loaded game + * @remarks Originally called tjouer + */ +void MortevielleEngine::playGame() { + gameLoaded(); + + // Loop handling actions until the game has to be quit, or show the lose or end sequence + do { + handleAction(); + if (shouldQuit()) + return; + } while (!((_quitGame) || (_endGame) || (_loseGame))); + + if (_endGame) + endGame(); + else if (_loseGame) + askRestart(); +} + +} // End of namespace Mortevielle diff --git a/engines/mortevielle/mortevielle.h b/engines/mortevielle/mortevielle.h new file mode 100644 index 0000000000..4d07d3000f --- /dev/null +++ b/engines/mortevielle/mortevielle.h @@ -0,0 +1,522 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This code is based on original Mortville Manor DOS source code + * Copyright (c) 1987-1989 Lankhor + */ + +#ifndef MORTEVIELLE_MORTEVIELLE_H +#define MORTEVIELLE_MORTEVIELLE_H + +#include "common/events.h" +#include "common/file.h" +#include "common/random.h" +#include "common/rect.h" +#include "common/stack.h" +#include "engines/advancedDetector.h" +#include "engines/engine.h" +#include "common/error.h" +#include "graphics/surface.h" +#include "mortevielle/debugger.h" +#include "mortevielle/dialogs.h" +#include "mortevielle/graphics.h" +#include "mortevielle/menu.h" +#include "mortevielle/mouse.h" +#include "mortevielle/saveload.h" +#include "mortevielle/sound.h" +#include "mortevielle/speech.h" +#include "mortevielle/outtext.h" + +namespace Mortevielle { + +/*---------------------------------------------------------------------------*/ +/*------------------- MEMORY MAP ------------------------*/ +/*---------------------------------------------------------------------------*/ +/* The following is a list of physical addresses in memory currently used + * by the game. + * + * Address + * ------- + * 5000:0 - Music data + * 6000:0 - Decompressed current image + * 7000:0+ - Compressed images + * 7000:2 - 16 words representing palette map + * 7000:4138 - width, height, x/y offset of decoded image + */ +const int kAdrMusic = 0x5000; + +// Debug channels +enum { + kMortevielleCore = 1 << 0, + kMortevielleGraphics = 1 << 1 +}; + +// Game languages +enum { + LANG_FRENCH = 0, + LANG_ENGLISH = 1, + LANG_GERMAN = 2 +}; + +// Static string list +enum { + S_YES_NO = 0, S_GO_TO = 1, S_SOMEONE_ENTERS = 2, S_COOL = 3, S_LOURDE = 4, + S_MALSAINE = 5, S_IDEM = 6, S_YOU = 7, S_ARE = 8, S_ALONE = 9, + S_HEAR_NOISE = 10, S_SHOULD_HAVE_NOTICED = 11, S_NUMBER_OF_HINTS = 12, + S_WANT_TO_WAKE_UP = 13, S_OK = 14, S_SAVE_LOAD = 15, S_RESTART = 18, S_F3 = 19, + S_F8 = 20, S_HIDE_SELF = 21, S_TAKE = 22, S_PROBE = 23, S_RAISE = 24, S_SUITE = 25, + S_STOP = 26, S_USE_DEP_MENU = 27, S_LIFT = 28, S_READ = 29, + S_LOOK = 30, S_SEARCH = 31, S_OPEN = 32, S_PUT = 33, S_TURN = 34, S_TIE = 35, S_CLOSE = 36, + S_HIT = 37, S_POSE = 38, S_SMASH = 39, + + S_SMELL = 40, S_SCRATCH = 41, S_PROBE2 = 42, S_BEFORE_USE_DEP_MENU = 43, S_DAY = 44 +}; + +enum DataType { + kStaticStrings = 0, + kGameStrings = 1 +}; + +#define SCREEN_WIDTH 640 +#define SCREEN_HEIGHT 400 +#define SCREEN_ORIG_HEIGHT 200 +#define MORT_DAT_REQUIRED_VERSION 1 +#define MORT_DAT "mort.dat" +#define GAME_FRAME_DELAY (1000 / 50) + +const int kTime1 = 410; +const int kTime2 = 250; + +const int kAcha = 492; +const int kFleche = 1758; + +const int kAsoul = 154; +const int kAouvr = 282; +const int kAchai = 387; +const int kArcf = 1272; +const int kArep = 1314; +const int kAmzon = 1650; +const int kArega = 0; + +const int kMaxDialogIndex = 9000; +const int kMaxDialogHint = 600; + +const int kDescriptionStringIndex = 0; // Unused +const int kInventoryStringIndex = 186; +const int kQuestionStringIndex = 247; +const int kDialogStringIndex = 292; +const int kMenuPlaceStringIndex = 435; +const int kMenuActionStringIndex = 476; +const int kMenuSelfStringIndex = 497; +const int kMenuSayStringIndex = 502; +const int kMaxPatt = 20; + +/* +9 "A glance at the forbidden$", +18 "It's already open$", +26 "A photograph$" +*/ +enum Places { + OWN_ROOM = 0, GREEN_ROOM = 1, PURPLE_ROOM = 2, TOILETS = 3, DARKBLUE_ROOM = 4, + BLUE_ROOM = 5, RED_ROOM = 6, BATHROOM = 7, GREEN_ROOM2 = 8, ROOM9 = 9, + DINING_ROOM = 10, BUREAU = 11, KITCHEN = 12, ATTIC = 13, CELLAR = 14, + LANDING = 15, CRYPT = 16, SECRET_PASSAGE = 17, ROOM18 = 18, MOUNTAIN = 19, + CHAPEL = 20, MANOR_FRONT = 21, MANOR_BACK = 22, INSIDE_WELL = 23, WELL = 24, + DOOR = 25, ROOM26 = 26, COAT_ARMS = 27 +}; + +enum GraphicModes { MODE_AMSTRAD1512 = 0, MODE_CGA = 1, MODE_EGA = 2, MODE_HERCULES = 3, MODE_TANDY = 4 }; + +struct nhom { + byte _id; /* number between 0 and 32 */ + byte _hom[4]; +}; + +struct CgaPalette { + byte _p; + nhom _a[16]; +}; + +struct Pattern { + byte _tay, _tax; + byte _des[kMaxPatt + 1][kMaxPatt + 1]; +}; + +struct SaveStruct { + int _faithScore; + byte _pctHintFound[11]; + byte _availableQuestion[43]; + byte _inventory[31]; + int _currPlace; + int _atticBallHoleObjectId; + int _atticRodHoleObjectId; + int _cellarObjectId; + int _secretPassageObjectId; + int _wellObjectId; + int _selectedObjectId; + int _purpleRoomObjectId; + int _cryptObjectId; + bool _alreadyEnteredManor; + byte _fullHour; +}; + +struct Hint { + int _hintId; + byte _point; +}; + +class MortevielleEngine : public Engine { +private: + const ADGameDescription *_gameDescription; + Common::Stack<int> _keypresses; + uint32 _lastGameFrame; + Common::Point _mousePos; + Common::StringArray _engineStrings; + Common::StringArray _gameStrings; + + Pattern _patternArr[15]; + int _menuOpcode; + + bool _mouseClick; + bool _inMainGameLoop; // Flag when the main game loop is active + bool _quitGame; // Quit game flag. Originally called 'arret' + bool _endGame; // End game flag. Originally called 'solu' + bool _loseGame; // Lose game flag. Originally called 'perdu' + bool _txxFileFl; // Flag used to determine if texts are from the original files or from a DAT file + bool _roomPresenceLuc; + bool _roomPresenceIda; + bool _purpleRoomPresenceLeo; + bool _roomPresenceGuy; + bool _roomPresenceEva; + bool _roomPresenceMax; + bool _roomPresenceBob; + bool _roomPresencePat; + bool _toiletsPresenceBobMax; + bool _bathRoomPresenceBobMax; + bool _room9PresenceLeo; + bool _hiddenHero; + bool _heroSearching; + bool _keyPressedEsc; + bool _reloadCFIEC; + bool _col; + bool _syn; + bool _obpart; + bool _anyone; + bool _uptodatePresence; + + int _textColor; + int _place; + int _manorDistance; + int _currBitIndex; + int _currDay; + int _currHour; + int _currHalfHour; + int _day; + int _hour; + int _minute; + int _mchai; + int _controlMenu; + int _startHour; + int _endHour; + Common::Point _stdPal[91][17]; + CgaPalette _cgaPal[91]; + + int _x26KeyCount; + int _roomDoorId; + int _openObjCount; + int _takeObjCount; + int _num; + int _searchCount; + bool _introSpeechPlayed; + int _inGameHourDuration; + int _x; + int _y; + int _currentHourCount; + int _currentDayHour; + + Common::String _hintPctMessage; + byte *_cfiecBuffer; + int _cfiecBufferSize; + int _openObjects[8]; + uint16 _dialogIndexArray[kMaxDialogIndex + 1]; + Hint _dialogHintArray[kMaxDialogHint + 1]; + + Common::ErrorCode initialize(); + Common::ErrorCode loadMortDat(); + void readStaticStrings(Common::File &f, int dataSize, DataType dataType); + void loadFont(Common::File &f); + bool handleEvents(); + void addKeypress(Common::Event &evt); + void initMouse(); + void showIntroduction(); + void mainGame(); + void playGame(); + void handleAction(); + void displayCGAPattern(int n, Pattern p, nhom *pal); + void loadPalette(); + void loadTexts(); + void loadBRUIT5(); + void loadCFIEC(); + void loadCFIPH(); + void showTitleScreen(); + int readclock(); + void palette(int v1); + int checkLeoMaxRandomPresence(); + void interactNPC(); + void initCaveOrCellar(); + void displayControlMenu(); + void displayItemInHand(int objId); + void resetRoomVariables(int roomId); + int getPresenceStats(int &rand, int faithScore, int roomId); + void setPresenceFlags(int roomId); + void testKey(bool d); + void exitRoom(); + void getReadDescription(int objId); + void getSearchDescription(int objId); + int checkLeaveSecretPassage(); + void changeGraphicalDevice(int newDevice); + void startDialog(int16 rep); + void endSearch(); + int convertCharacterIndexToBitIndex(int characterIndex); + int convertBitIndexToCharacterIndex(int bitIndex); + void clearUpperLeftPart(); + void clearDescriptionBar(); + void clearVerbBar(); + void clearUpperRightPart(); + int getRandomNumber(int minval, int maxval); + void showMoveMenuAlert(); + void showConfigScreen(); + void decodeNumber(byte *pStart, int count); + void resetVariables(); + void music(); + void drawRightFrame(); + void prepareRoom(); + void drawClock(); + void checkManorDistance(); + void gotoManorFront(); + void gotoManorBack(); + void gotoDiningRoom(); + bool checkInventory(int objectId); + void loseGame(); + void floodedInWell(); + void displayDiningRoom(); + void startMusicOrSpeech(int so); + void setTextColor(int col); + void prepareScreenType1(); + void prepareScreenType2(); + void prepareScreenType3(); + void updateHour(int &day, int &hour, int &minute); + void getKnockAnswer(); + int getPresenceStatsGreenRoom(); + int getPresenceStatsPurpleRoom(); + int getPresenceStatsToilets(); + int getPresenceStatsBlueRoom(); + int getPresenceStatsRedRoom(); + int getPresenceStatsDiningRoom(int &hour); + int getPresenceStatsBureau(int &hour); + int getPresenceStatsKitchen(); + int getPresenceStatsAttic(); + int getPresenceStatsLanding(); + int getPresenceStatsChapel(int &hour); + int getPresenceBitIndex(int roomId); + void setPresenceGreenRoom(int roomId); + void setPresencePurpleRoom(); + void setPresenceBlueRoom(); + void setPresenceRedRoom(int roomId); + int setPresenceDiningRoom(int hour); + int setPresenceBureau(int hour); + int setPresenceKitchen(); + int setPresenceLanding(); + int setPresenceChapel(int hour); + void setRandomPresenceGreenRoom(int faithScore); + void setRandomPresencePurpleRoom(int faithScore); + void setRandomPresenceBlueRoom(int faithScore); + void setRandomPresenceRedRoom(int faithScore); + void setRandomPresenceRoom9(int faithScore); + void setRandomPresenceDiningRoom(int faithScore); + void setRandomPresenceBureau(int faithScore); + void setRandomPresenceKitchen(int faithScore); + void setRandomPresenceAttic(int faithScore); + void setRandomPresenceLanding(int faithScore); + void setRandomPresenceChapel(int faithScore); + void loadPlaces(); + void resetPresenceInRooms(int roomId); + void showPeoplePresent(int bitIndex); + int selectCharacters(int min, int max); + void fctMove(); + void fctTake(); + void fctInventoryTake(); + void fctLift(); + void fctRead(); + void fctSelfRead(); + void fctLook(); + void fctSelftLook(); + void fctSearch(); + void fctSelfSearch(); + void fctOpen(); + void fctPlace(); + void fctTurn(); + void fctSelfHide(); + void fctAttach(); + void fctClose(); + void fctKnock(); + void fctSelfPut(); + void fctListen(); + void fctEat(); + void fctEnter(); + void fctSleep(); + void fctForce(); + void fctLeave(); + void fctWait(); + void fctSound(); + void fctDiscuss(); + void fctSmell(); + void fctScratch(); + void endGame(); + void askRestart(); + void handleOpcode(); + void prepareDisplayText(); + bool decryptNextChar(char &c, int &idx, byte &pt); + void displayStatusArrow(); + void displayStatusInDescriptionBar(char stat); + void displayQuestionText(Common::String s, int cmd); + void displayTextInDescriptionBar(int x, int y, int nb, int mesgId); + void displayTextInVerbBar(Common::String text); + void mapMessageId(int &mesgId); + void resetOpenObjects(); + void setCoordinates(int sx); + void drawPicture(); + void drawPictureWithText(); + void addObjectToInventory(int objectId); + void putInHand(int &objId); + void initMaxAnswer(); + void displayAnimFrame(int frameNum, int animId); + + void copcha(); + void adzon(); + void premtet(); + void ajchai(); + void ecr2(Common::String text); + void tlu(int af, int ob); + void mennor(); + void tsuiv(); + void treg(int objId); + int rechai(); + +public: + Common::Point _prevPos; + int _currMenu; + int _currAction; + int _drawingSizeArr[108]; + int _charAnswerCount[9]; + int _charAnswerMax[9]; + byte _tabdon[4001]; + bool _soundOff; + bool _blo; + bool _destinationOk; + bool _largestClearScreen; + int _currGraphicalDevice; + int _newGraphicalDevice; + float _addFix; + int _savedBitIndex; + int _numpal; + int _key; + SaveStruct _coreVar, _saveStruct; + + int _maff; + int _caff; + int _crep; + + int _resolutionScaler; + byte _destinationArray[7][25]; + + // TODO: Replace the following with proper implementations, or refactor out the code using them + byte _mem[65536 * 16]; + byte *_curPict; + byte *_curAnim; + byte *_rightFramePict; + byte *_compMusicBuf1; + byte *_compMusicBuf2; + + Debugger _debugger; + ScreenSurface _screenSurface; + PaletteManager _paletteManager; + GfxSurface _backgroundSurface; + Common::RandomSource _randomSource; + SoundManager _soundManager; + SavegameManager _savegameManager; + SpeechManager _speechManager; + Menu _menu; + MouseHandler _mouse; + TextHandler _text; + DialogManager _dialogManager; + + MortevielleEngine(OSystem *system, const ADGameDescription *gameDesc); + ~MortevielleEngine(); + virtual bool hasFeature(EngineFeature f) const; + virtual bool canLoadGameStateCurrently(); + virtual bool canSaveGameStateCurrently(); + virtual Common::Error loadGameState(int slot); + virtual Common::Error saveGameState(int slot, const Common::String &desc); + virtual Common::Error run(); + uint32 getGameFlags() const; + Common::Language getLanguage() const; + static Common::String generateSaveFilename(const Common::String &target, int slot); + Common::String generateSaveFilename(int slot) { return generateSaveFilename(_targetName, slot); } + + int getChar(); + bool keyPressed(); + Common::Point getMousePos() const { return _mousePos; } + void setMousePos(const Common::Point &pt); + bool getMouseClick() const { return _mouseClick; } + void setMouseClick(bool v) { _mouseClick = v; } + Common::String getEngineString(int idx) const { return _engineStrings[idx]; } + Common::String getGameString(int idx) const { return _gameStrings[idx]; } + + void delay(int amount); + void gameLoaded(); + void initGame(); + void displayAloneText(); + void draw(int x, int y); + void charToHour(); + void hourToChar(); + Common::String getString(int num); + void setPal(int n); + Common::String copy(const Common::String &s, int idx, size_t size); + void testKeyboard(); + int getPresence(int roomId); + void displayEmptyHand(); + void displayPicture(const byte *pic, int x, int y); + + int gettKeyPressed(); + void handleDescriptionText(int f, int mesgId); + int getAnimOffset(int frameNum, int animNum); + + void hirs(); +}; + +extern MortevielleEngine *g_vm; + +} // End of namespace Mortevielle + +#endif diff --git a/engines/mortevielle/mouse.cpp b/engines/mortevielle/mouse.cpp new file mode 100644 index 0000000000..dfc9ccd706 --- /dev/null +++ b/engines/mortevielle/mouse.cpp @@ -0,0 +1,273 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This code is based on original Mortville Manor DOS source code + * Copyright (c) 1987-1989 Lankhor + */ + +#include "mortevielle/mortevielle.h" +#include "mortevielle/mouse.h" + +#include "common/endian.h" +#include "common/rect.h" + +namespace Mortevielle { + +/** + * Initialize the mouse + * @remarks Originally called 'init_mouse' + */ +void MouseHandler::initMouse() { + _counter = 0; + _pos = Common::Point(0, 0); + + _vm->setMouseClick(false); +} + +/** + * Backs up the area behind where the mouse cursor is to be drawn + * @remarks Originally called 'hide_mouse' + */ +void MouseHandler::hideMouse() { + // No implementation needed in ScummVM +} + +/** + * Draws the mouse cursor + * @remarks Originally called 'show_mouse' + */ +void MouseHandler::showMouse() { + // ScummVM implementation uses CursorMan for drawing the cursor +} + +/** + * Set mouse position + * @remarks Originally called 'pos_mouse' + */ +void MouseHandler::setMousePosition(Common::Point newPos) { + if (newPos.x > 314 * _vm->_resolutionScaler) + newPos.x = 314 * _vm->_resolutionScaler; + else if (newPos.x < 0) + newPos.x = 0; + if (newPos.y > 199) + newPos.y = 199; + else if (newPos.y < 0) + newPos.y = 0; + if (newPos == _pos) + return; + + // Set the new position + _vm->setMousePos(newPos); +} + +/** + * Get mouse poisition + * @remarks Originally called 'read_pos_mouse' + */ +void MouseHandler::getMousePosition(int &x, int &y, bool &click) { + x = _vm->getMousePos().x; + y = _vm->getMousePos().y; + click = _vm->getMouseClick(); +} + +/** + * Move mouse + * @remarks Originally called 'mov_mouse' + */ +void MouseHandler::moveMouse(bool &funct, char &key) { + bool p_key; + char in1, in2; + int cx, cy; + bool click; + + // Set defaults and check pending events + funct = false; + key = '\377'; + p_key = _vm->keyPressed(); + + // If mouse button clicked, return it + if (_vm->getMouseClick()) + return; + + // Handle any pending keypresses + while (p_key) { + if (_vm->shouldQuit()) + return; + + in1 = _vm->getChar(); + getMousePosition(cx, cy, click); + switch (toupper(in1)) { + case '4': + cx -= 8; + break; + case '2': + cy += 8; + break; + case '6': + cx += 8; + break; + case '8': + cy -= 8; + break; + case '7': + cy = 1; + cx = 1; + break; + case '1': + cx = 1; + cy = 190; + break; + case '9': + cx = 315 * _vm->_resolutionScaler; + cy = 1; + break; + case '3': + cy = 190; + cx = 315 * _vm->_resolutionScaler; + break; + case '5': + cy = 100; + cx = 155 * _vm->_resolutionScaler; + break; + case ' ': + case '\15': + _vm->setMouseClick(true); + return; + break; + case '\33': + p_key = _vm->keyPressed(); + + if (p_key) { + in2 = _vm->getChar(); + + if ((in2 >= ';') && (in2 <= 'D')) { + funct = true; + key = in2; + return; + } else { + switch (in2) { + case 'K': + --cx; + break; + case 'P': + ++cy; + break; + case 'M': + cx += 2; + break; + case 'H': + --cy; + break; + case 'G': + --cx; + --cy; + break; + case 'I': + ++cx; + --cy; + break; + case 'O': + --cx; + ++cy; + break; + case 'Q': + ++cx; + ++cy; + break; + default: + break; + } // case + } + } + break; + case 'I': + cx = _vm->_resolutionScaler * 32; + cy = 8; + break; + case 'D': + cx = 80 * _vm->_resolutionScaler; + cy = 8; + break; + case 'A': + cx = 126 * _vm->_resolutionScaler; + cy = 8; + break; + case 'S': + cx = 174 * _vm->_resolutionScaler; + cy = 8; + break; + case 'P': + cx = 222 * _vm->_resolutionScaler; + cy = 8; + break; + case 'F': + cx = _vm->_resolutionScaler * 270; + cy = 8; + break; + case '\23': + _vm->_soundOff = !_vm->_soundOff; + return; + break; + case '\24': // ^T => mode tandy + funct = true; + key = '\11'; + break; + case '\10': // ^H => mode Hercule + funct = true; + key = '\7'; + break; + case '\1': + case '\3': + case '\5': + funct = true; + key = in1; + break; + default: + break; + } + + setMousePosition(Common::Point(cx, cy)); + p_key = _vm->keyPressed(); + } +} + +/** + * Mouse function : Is mouse in a given rect? + * @remarks Originally called 'dans_rect' + */ +bool MouseHandler::isMouseIn(Common::Rect r) { + int x, y; + bool click; + + getMousePosition(x, y, click); + if ((x > r.left) && (x < r.right) && (y > r.top) && (y < r.bottom)) + return true; + + return false; +} + +void MouseHandler::setParent(MortevielleEngine *vm) { + _vm = vm; +} + +} // End of namespace Mortevielle diff --git a/engines/mortevielle/mouse.h b/engines/mortevielle/mouse.h new file mode 100644 index 0000000000..1b9856e2c4 --- /dev/null +++ b/engines/mortevielle/mouse.h @@ -0,0 +1,56 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This code is based on original Mortville Manor DOS source code + * Copyright (c) 1987-1989 Lankhor + */ + +#ifndef MORTEVIELLE_MOUSE_H +#define MORTEVIELLE_MOUSE_H + +#include "common/rect.h" + +namespace Mortevielle { +class MortevielleEngine; + +class MouseHandler { +private: + MortevielleEngine *_vm; + + int s_s[12][6]; + int _counter; +public: + Common::Point _pos; + + void setParent(MortevielleEngine *vm); + void initMouse(); + void hideMouse(); + void showMouse(); + void setMousePosition(Common::Point newPos); + void getMousePosition(int &x, int &y, bool &click); + void moveMouse(bool &funct, char &key); + bool isMouseIn(Common::Rect r); +}; + +} // End of namespace Mortevielle +#endif diff --git a/engines/mortevielle/outtext.cpp b/engines/mortevielle/outtext.cpp new file mode 100644 index 0000000000..99c06c7c4c --- /dev/null +++ b/engines/mortevielle/outtext.cpp @@ -0,0 +1,330 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This code is based on original Mortville Manor DOS source code + * Copyright (c) 1987-1989 Lankhor + */ + +#include "mortevielle/mortevielle.h" +#include "mortevielle/mouse.h" +#include "mortevielle/outtext.h" +#include "mortevielle/graphics.h" + +#include "common/file.h" +#include "common/str.h" + +namespace Mortevielle { + +/** + * Next word + * @remarks Originally called 'l_motsuiv' + */ +int TextHandler::nextWord(int p, const char *ch, int &tab) { + int c = p; + + while ((ch[p] != ' ') && (ch[p] != '$') && (ch[p] != '@')) + ++p; + + return tab * (p - c); +} + +/** + * Engine function - Display Text + * @remarks Originally called 'afftex' + */ +void TextHandler::displayStr(Common::String inputStr, int x, int y, int dx, int dy, int typ) { + int tab; + Common::String s; + int i, j; + + // Safeguard: add $ just in case + inputStr += '$'; + + _vm->_screenSurface.putxy(x, y); + if (_vm->_resolutionScaler == 1) + tab = 10; + else + tab = 6; + dx *= 6; + dy *= 6; + int xc = x; + int yc = y; + int xf = x + dx; + int yf = y + dy; + int p = 0; + bool stringParsed = (inputStr[p] == '$'); + s = ""; + while (!stringParsed) { + switch (inputStr[p]) { + case '@': + _vm->_screenSurface.drawString(s, typ); + s = ""; + ++p; + xc = x; + yc += 6; + _vm->_screenSurface.putxy(xc, yc); + break; + case ' ': + s += ' '; + xc += tab; + ++p; + if (nextWord(p, inputStr.c_str(), tab) + xc > xf) { + _vm->_screenSurface.drawString(s, typ); + s = ""; + xc = x; + yc += 6; + if (yc > yf) { + while (!_vm->keyPressed()) + ; + i = y; + do { + j = x; + do { + _vm->_screenSurface.putxy(j, i); + _vm->_screenSurface.drawString(" ", 0); + j += 6; + } while (j <= xf); + i += 6; + } while (i <= yf); + yc = y; + } + _vm->_screenSurface.putxy(xc, yc); + } + break; + case '$': + stringParsed = true; + _vm->_screenSurface.drawString(s, typ); + break; + default: + s += inputStr[p]; + ++p; + xc += tab; + break; + } + } +} + +/** + * Load DES (picture container) file + * @remarks Originally called 'chardes' + */ +void TextHandler::loadPictureFile(Common::String filename, Common::String altFilename, int32 skipSize, int length) { + Common::File f; + if (!f.open(filename)) { + if (!f.open(altFilename)) + error("Missing file: Either %s or %s", filename.c_str(), altFilename.c_str()); + } + // HACK: The original game contains a bug in the 2nd intro screen, in German DOS version. + // The size specified in the fxx array is wrong (too short). In order to fix it, we are using + // the value -1 to force a variable read length. + if (length == -1) + length = f.size() - skipSize; + + assert(skipSize + length <= f.size()); + + free(_vm->_curPict); + _vm->_curPict = (byte *)malloc(sizeof(byte) * length); + f.seek(skipSize); + f.read(_vm->_curPict, length); + f.close(); +} + +/** + * Load ANI file + * @remarks Originally called 'charani' + */ +void TextHandler::loadAniFile(Common::String filename, int32 skipSize, int length) { + Common::File f; + if (!f.open(filename)) + error("Missing file - %s", filename.c_str()); + + assert(skipSize + length <= f.size()); + + free(_vm->_curAnim); + _vm->_curAnim = (byte *)malloc(sizeof(byte) * length); + f.seek(skipSize); + f.read(_vm->_curAnim, length); + f.close(); +} + +void TextHandler::taffich() { + static const byte rang[16] = {15, 14, 11, 7, 13, 12, 10, 6, 9, 5, 3, 1, 2, 4, 8, 0}; + + static const byte tran1[] = { 121, 121, 138, 139, 120 }; + static const byte tran2[] = { 150, 150, 152, 152, 100, 110, 159, 100, 100 }; + + int cx, drawingSize, npal; + int32 drawingStartPos; + int alllum[16]; + + int a = _vm->_caff; + if ((a >= 153) && (a <= 161)) + a = tran2[a - 153]; + else if ((a >= 136) && (a <= 140)) + a = tran1[a - 136]; + int b = a; + if (_vm->_maff == a) + return; + + switch (a) { + case 16: + _vm->_coreVar._pctHintFound[9] = '*'; + _vm->_coreVar._availableQuestion[42] = '*'; + break; + case 20: + _vm->_coreVar._availableQuestion[39] = '*'; + if (_vm->_coreVar._availableQuestion[36] == '*') { + _vm->_coreVar._pctHintFound[3] = '*'; + _vm->_coreVar._availableQuestion[38] = '*'; + } + break; + case 24: + _vm->_coreVar._availableQuestion[37] = '*'; + break; + case 30: + _vm->_coreVar._availableQuestion[9] = '*'; + break; + case 31: // Coat of arms + _vm->_coreVar._pctHintFound[4] = '*'; + _vm->_coreVar._availableQuestion[35] = '*'; + break; + case 118: + _vm->_coreVar._availableQuestion[41] = '*'; + break; + case 143: + _vm->_coreVar._pctHintFound[1] = '*'; + break; + case 150: + _vm->_coreVar._availableQuestion[34] = '*'; + break; + case 151: + _vm->_coreVar._pctHintFound[2] = '*'; + break; + default: + break; + } + + _vm->_destinationOk = true; + _vm->_mouse.hideMouse(); + drawingStartPos = 0; + Common::String filename, altFilename; + + if ((a != 50) && (a != 51)) { + _vm->_maff = a; + if (a == 159) + a = 86; + else if (a > 140) + a -= 67; + else if (a > 137) + a -= 66; + else if (a > 99) + a -= 64; + else if (a > 69) + a -= 42; + else if (a > 29) + a -= 5; + else if (a == 26) + a = 24; + else if (a > 18) + --a; + npal = a; + + for (cx = 0; cx <= (a - 1); ++cx) + drawingStartPos += _vm->_drawingSizeArr[cx]; + drawingSize = _vm->_drawingSizeArr[a]; + + altFilename = filename = "DXX.mor"; + } else { + filename = "DZZ.mor"; + altFilename = "DZZALL"; + + if (a == 50) { + // First intro screen + drawingStartPos = 0; + drawingSize = _vm->_drawingSizeArr[87]; + } else { // a == 51 + // Second intro screen + drawingStartPos = _vm->_drawingSizeArr[87]; + // HACK: Force a variable size in order to fix the wrong size used by the German version + drawingSize = -1; + } + _vm->_maff = a; + npal = a + 37; + } + loadPictureFile(filename, altFilename, drawingStartPos, drawingSize); + if (_vm->_currGraphicalDevice == MODE_HERCULES) { + for (int i = 0; i <= 15; ++i) { + int palh = READ_LE_UINT16(&_vm->_curPict[2 + (i << 1)]); + alllum[i] = (palh & 15) + (((uint)palh >> 12) & 15) + (((uint)palh >> 8) & 15); + } + for (int i = 0; i <= 15; ++i) { + int k = 0; + for (int j = 0; j <= 15; ++j) { + if (alllum[j] > alllum[k]) + k = j; + } + _vm->_curPict[2 + (k << 1)] = rang[i]; + alllum[k] = -1; + } + } + _vm->_numpal = npal; + _vm->setPal(npal); + + if ((b < 15) || (b == 16) || (b == 17) || (b == 24) || (b == 26) || (b == 50)) { + drawingStartPos = 0; + if ((b < 15) || (b == 16) || (b == 17) || (b == 24) || (b == 26)) { + if (b == 26) + b = 18; + else if (b == 24) + b = 17; + else if (b > 15) + --b; + for (cx = 0; cx <= (b - 1); ++cx) + drawingStartPos += _vm->_drawingSizeArr[cx + 89]; + drawingSize = _vm->_drawingSizeArr[b + 89]; + filename = "AXX.mor"; + } else { // b == 50 + // CHECKME: the size of AZZ.mor is 1280 for the DOS version + // and 1260 for the Amiga version. Maybe the 20 bytes + // are a filler (to get 10 blocks of 128 bytes), + // or the size should be variable. + drawingSize = 1260; + filename = "AZZ.mor"; + } + loadAniFile(filename, drawingStartPos, drawingSize); + } + _vm->_mouse.showMouse(); + if ((a < COAT_ARMS) && ((_vm->_maff < COAT_ARMS) || (_vm->_coreVar._currPlace == LANDING)) && (_vm->_currAction != OPCODE_ENTER)) { + if ((a == ATTIC) || (a == CELLAR)) + _vm->displayAloneText(); + else if (!_vm->_blo) + _vm->getPresence(_vm->_coreVar._currPlace); + _vm->_savedBitIndex = 0; + } +} + +void TextHandler::setParent(MortevielleEngine *vm) { + _vm = vm; +} + +} // End of namespace Mortevielle diff --git a/engines/mortevielle/outtext.h b/engines/mortevielle/outtext.h new file mode 100644 index 0000000000..44868036d5 --- /dev/null +++ b/engines/mortevielle/outtext.h @@ -0,0 +1,49 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This code is based on original Mortville Manor DOS source code + * Copyright (c) 1987-1989 Lankhor + */ + +#ifndef MORTEVIELLE_OUTTEXT_H +#define MORTEVIELLE_OUTTEXT_H + +#include "common/str.h" + +namespace Mortevielle { +class MortevielleEngine; + +class TextHandler { +private: + MortevielleEngine *_vm; + int nextWord(int p, const char *ch, int &tab); +public: + void setParent(MortevielleEngine *vm); + void displayStr(Common::String inputStr, int x, int y, int dx, int dy, int typ); + void loadPictureFile(Common::String filename, Common::String altFilename, int32 skipSize, int length); + void loadAniFile(Common::String filename, int32 skipSize, int length); + void taffich(); +}; + +} // End of namespace Mortevielle +#endif diff --git a/engines/mortevielle/saveload.cpp b/engines/mortevielle/saveload.cpp new file mode 100644 index 0000000000..ff3bee5c06 --- /dev/null +++ b/engines/mortevielle/saveload.cpp @@ -0,0 +1,328 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This code is based on original Mortville Manor DOS source code + * Copyright (c) 1987-1989 Lankhor + */ + +#include "mortevielle/mortevielle.h" +#include "mortevielle/dialogs.h" +#include "mortevielle/mouse.h" +#include "mortevielle/saveload.h" + +#include "common/file.h" +#include "common/system.h" + +namespace Mortevielle { + +static const char SAVEGAME_ID[4] = { 'M', 'O', 'R', 'T' }; + +void SavegameManager::setParent(MortevielleEngine *vm) { + _vm = vm; +} + +/** + * Handle saving or loading savegame data + */ +void SavegameManager::sync_save(Common::Serializer &sz) { + sz.syncAsSint16LE(g_vm->_saveStruct._faithScore); + for (int i = 0; i < 11; ++i) + sz.syncAsByte(g_vm->_saveStruct._pctHintFound[i]); + for (int i = 0; i < 43; ++i) + sz.syncAsByte(g_vm->_saveStruct._availableQuestion[i]); + for (int i = 0; i < 31; ++i) + sz.syncAsByte(g_vm->_saveStruct._inventory[i]); + + sz.syncAsSint16LE(g_vm->_saveStruct._currPlace); + sz.syncAsSint16LE(g_vm->_saveStruct._atticBallHoleObjectId); + sz.syncAsSint16LE(g_vm->_saveStruct._atticRodHoleObjectId); + sz.syncAsSint16LE(g_vm->_saveStruct._cellarObjectId); + sz.syncAsSint16LE(g_vm->_saveStruct._secretPassageObjectId); + sz.syncAsSint16LE(g_vm->_saveStruct._wellObjectId); + sz.syncAsSint16LE(g_vm->_saveStruct._selectedObjectId); + sz.syncAsSint16LE(g_vm->_saveStruct._purpleRoomObjectId); + sz.syncAsSint16LE(g_vm->_saveStruct._cryptObjectId); + sz.syncAsByte(g_vm->_saveStruct._alreadyEnteredManor); + sz.syncAsByte(g_vm->_saveStruct._fullHour); + + sz.syncBytes(_tabdonSaveBuffer, 391); +} + +/** + * Inner code for loading a saved game + * @remarks Originally called 'takesav' + */ +void SavegameManager::loadSavegame(const Common::String &filename) { + // Try loading first from the save area + Common::SeekableReadStream *stream = g_system->getSavefileManager()->openForLoading(filename); + + Common::File f; + if (stream == NULL) { + if (!f.open(filename)) + error("Unable to open save file '%s'", filename.c_str()); + + stream = f.readStream(f.size()); + f.close(); + } + + // Check whether it's a ScummVM saved game + char buffer[4]; + stream->read(buffer, 4); + if (!strncmp(&buffer[0], &SAVEGAME_ID[0], 4)) { + // Yes, it is, so skip over the savegame header + SavegameHeader header; + readSavegameHeader(stream, header); + delete header.thumbnail; + } else { + stream->seek(0); + } + + // Read the game contents + Common::Serializer sz(stream, NULL); + sync_save(sz); + + g_vm->_coreVar = g_vm->_saveStruct; + for (int i = 0; i <= 389; ++i) + g_vm->_tabdon[i + kAcha] = _tabdonSaveBuffer[i]; + + // Close the stream + delete stream; +} + +/** + * Load a saved game + */ +Common::Error SavegameManager::loadGame(const Common::String &filename) { + g_vm->_mouse.hideMouse(); + g_vm->displayEmptyHand(); + loadSavegame(filename); + + /* Initialization */ + g_vm->charToHour(); + g_vm->initGame(); + g_vm->gameLoaded(); + g_vm->_mouse.showMouse(); + return Common::kNoError; +} + +/** + * Save the game + */ +Common::Error SavegameManager::saveGame(int n, const Common::String &saveName) { + Common::OutSaveFile *f; + int i; + + g_vm->_mouse.hideMouse(); + g_vm->hourToChar(); + + for (i = 0; i <= 389; ++i) + _tabdonSaveBuffer[i] = g_vm->_tabdon[i + kAcha]; + g_vm->_saveStruct = g_vm->_coreVar; + if (g_vm->_saveStruct._currPlace == ROOM26) + g_vm->_saveStruct._currPlace = LANDING; + + Common::String filename = _vm->generateSaveFilename(n); + f = g_system->getSavefileManager()->openForSaving(filename); + + // Write out the savegame header + f->write(&SAVEGAME_ID[0], 4); + + // Write out the header + SavegameHeader header; + writeSavegameHeader(f, saveName); + + // Write out the savegame contents + Common::Serializer sz(NULL, f); + sync_save(sz); + + // Close the save file + f->finalize(); + delete f; + + // Skipped: dialog asking to swap floppy + + g_vm->_mouse.showMouse(); + return Common::kNoError; +} + +Common::Error SavegameManager::loadGame(int slot) { + return loadGame(_vm->generateSaveFilename(slot)); +} + +Common::Error SavegameManager::saveGame(int slot) { + return saveGame(slot, _vm->generateSaveFilename(slot)); +} + +void SavegameManager::writeSavegameHeader(Common::OutSaveFile *out, const Common::String &saveName) { + // Write out a savegame header + out->writeByte(SAVEGAME_VERSION); + + // Write savegame name + out->writeString(saveName); + out->writeByte(0); + + // Get the active palette + uint8 thumbPalette[256 * 3]; + g_system->getPaletteManager()->grabPalette(thumbPalette, 0, 256); + + // Create a thumbnail and save it + Graphics::Surface *thumb = new Graphics::Surface(); + Graphics::Surface s = g_vm->_screenSurface.lockArea(Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)); + + ::createThumbnail(thumb, (const byte *)s.pixels, SCREEN_WIDTH, SCREEN_HEIGHT, thumbPalette); + Graphics::saveThumbnail(*out, *thumb); + thumb->free(); + delete thumb; + + // Write out the save date/time + TimeDate td; + g_system->getTimeAndDate(td); + out->writeSint16LE(td.tm_year + 1900); + out->writeSint16LE(td.tm_mon + 1); + out->writeSint16LE(td.tm_mday); + out->writeSint16LE(td.tm_hour); + out->writeSint16LE(td.tm_min); +} + +bool SavegameManager::readSavegameHeader(Common::InSaveFile *in, SavegameHeader &header) { + header.thumbnail = NULL; + + // Get the savegame version + header.version = in->readByte(); + + // Read in the save name + header.saveName.clear(); + char ch; + while ((ch = (char)in->readByte()) != '\0') + header.saveName += ch; + + // Get the thumbnail + header.thumbnail = Graphics::loadThumbnail(*in); + if (!header.thumbnail) + return false; + + // Read in save date/time + header.saveYear = in->readSint16LE(); + header.saveMonth = in->readSint16LE(); + header.saveDay = in->readSint16LE(); + header.saveHour = in->readSint16LE(); + header.saveMinutes = in->readSint16LE(); + + return true; +} + +SaveStateList SavegameManager::listSaves(const Common::String &target) { + Common::String pattern = target; + pattern += ".???"; + + Common::StringArray files = g_system->getSavefileManager()->listSavefiles(pattern); + sort(files.begin(), files.end()); // Sort (hopefully ensuring we are sorted numerically..) + + SaveStateList saveList; + for (Common::StringArray::const_iterator file = files.begin(); file != files.end(); ++file) { + // Obtain the last 3 digits of the filename, since they correspond to the save slot + const Common::String &fname = *file; + int slotNumber = atoi(fname.c_str() + fname.size() - 3); + + Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(fname); + if (in) { + // There can be two types of savegames: original interpreter savegames, and ScummVM savegames. + // Original interpreter savegames are 497 bytes, and still need to be supported because the + // initial game state is stored as a savegame + bool validFlag = false; + Common::String saveDescription; + + char buffer[4]; + in->read(buffer, 4); + if (!strncmp(&buffer[0], &SAVEGAME_ID[0], 4)) { + // ScummVm savegame. Read in the header to get the savegame name + SavegameHeader header; + validFlag = readSavegameHeader(in, header); + + if (validFlag) { + delete header.thumbnail; + saveDescription = header.saveName; + } + } else if (file->size() == 497) { + // Form an appropriate savegame name + saveDescription = (slotNumber == 0) ? "Initial game state" : + Common::String::format("Savegame #%d", slotNumber); + validFlag = true; + } + + if (validFlag) + // Got a valid savegame + saveList.push_back(SaveStateDescriptor(slotNumber, saveDescription)); + + delete in; + } + } + + return saveList; +} + +SaveStateDescriptor SavegameManager::querySaveMetaInfos(const Common::String &fileName) { + Common::InSaveFile *f = g_system->getSavefileManager()->openForLoading(fileName); + + if (f) { + // Get the slot number + int slot = 1; + if (fileName.size() > 4 && fileName[fileName.size() - 4] == '.') + slot = atoi(fileName.c_str() + fileName.size() - 3); + + // Check to see if it's a ScummVM savegame or not + char buffer[4]; + f->read(buffer, 4); + + bool hasHeader = !strncmp(&buffer[0], &SAVEGAME_ID[0], 4); + + if (!hasHeader) { + // Original savegame perhaps? + delete f; + + SaveStateDescriptor desc(slot, Common::String::format("Savegame - %s", slot)); + desc.setDeletableFlag(slot != 0); + desc.setWriteProtectedFlag(slot == 0); + return desc; + } else { + // Get the savegame header information + SavegameHeader header; + readSavegameHeader(f, header); + delete f; + + // Create the return descriptor + SaveStateDescriptor desc(slot, header.saveName); + desc.setDeletableFlag(true); + desc.setWriteProtectedFlag(false); + desc.setThumbnail(header.thumbnail); + desc.setSaveDate(header.saveYear, header.saveMonth, header.saveDay); + desc.setSaveTime(header.saveHour, header.saveMinutes); + + return desc; + } + } + + return SaveStateDescriptor(); +} + +} // End of namespace Mortevielle diff --git a/engines/mortevielle/saveload.h b/engines/mortevielle/saveload.h new file mode 100644 index 0000000000..0121a04d8e --- /dev/null +++ b/engines/mortevielle/saveload.h @@ -0,0 +1,73 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This code is based on original Mortville Manor DOS source code + * Copyright (c) 1987-1989 Lankhor + */ + +#ifndef MORTEVIELLE_SAVELOAD_H +#define MORTEVIELLE_SAVELOAD_H + +#include "common/savefile.h" +#include "common/serializer.h" +#include "graphics/palette.h" +#include "graphics/scaler.h" +#include "graphics/thumbnail.h" + +#define SAVEGAME_VERSION 1 + +namespace Mortevielle { + +struct SavegameHeader { + uint8 version; + Common::String saveName; + Graphics::Surface *thumbnail; + int saveYear, saveMonth, saveDay; + int saveHour, saveMinutes; + int totalFrames; +}; + +class MortevielleEngine; + +class SavegameManager { +private: + MortevielleEngine *_vm; + byte _tabdonSaveBuffer[391]; + + void sync_save(Common::Serializer &sz); +public: + void setParent(MortevielleEngine *vm); + void loadSavegame(const Common::String &filename); + Common::Error loadGame(const Common::String &filename); + Common::Error saveGame(int n, const Common::String &saveName); + Common::Error loadGame(int slot); + Common::Error saveGame(int slot); + + void writeSavegameHeader(Common::OutSaveFile *out, const Common::String &saveName); + static bool readSavegameHeader(Common::InSaveFile *in, SavegameHeader &header); + static SaveStateList listSaves(const Common::String &target); + static SaveStateDescriptor querySaveMetaInfos(const Common::String &fileName); +}; + +} // End of namespace Mortevielle +#endif diff --git a/engines/mortevielle/sound.cpp b/engines/mortevielle/sound.cpp new file mode 100644 index 0000000000..f9b53ffed7 --- /dev/null +++ b/engines/mortevielle/sound.cpp @@ -0,0 +1,205 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This code is based on original Mortville Manor DOS source code + * Copyright (c) 1987-1989 Lankhor + */ + +#include "mortevielle/mortevielle.h" +#include "mortevielle/sound.h" + +#include "common/scummsys.h" + +namespace Mortevielle { + +/** + * Constructor + */ +PCSpeaker::PCSpeaker(int rate) { + _rate = rate; + _oscLength = 0; + _oscSamples = 0; + _remainingSamples = 0; + _volume = 255; +} + +/** + * Destructor + */ +PCSpeaker::~PCSpeaker() { +} + +/** + * Adds a new note to the queue of notes to be played. + */ +void PCSpeaker::play(int freq, uint32 length) { + assert((freq > 0) && (length > 0)); + Common::StackLock lock(_mutex); + + _pendingNotes.push(SpeakerNote(freq, length)); +} + +/** + * Stops the currently playing song + */ +void PCSpeaker::stop() { + Common::StackLock lock(_mutex); + + _remainingSamples = 0; + _pendingNotes.clear(); +} + +void PCSpeaker::setVolume(byte volume) { + _volume = volume; +} + +/** + * Return true if a song is currently playing + */ +bool PCSpeaker::isPlaying() const { + return !_pendingNotes.empty() || (_remainingSamples != 0); +} + +/** + * Method used by the mixer to pull off pending samples to play + */ +int PCSpeaker::readBuffer(int16 *buffer, const int numSamples) { + Common::StackLock lock(_mutex); + + int i; + + for (i = 0; (_remainingSamples || !_pendingNotes.empty()) && (i < numSamples); ++i) { + if (!_remainingSamples) + // Used up the current note, so queue the next one + dequeueNote(); + + buffer[i] = generateSquare(_oscSamples, _oscLength) * _volume; + if (_oscSamples++ >= _oscLength) + _oscSamples = 0; + + _remainingSamples--; + } + + // Clear the rest of the buffer + if (i < numSamples) + memset(buffer + i, 0, (numSamples - i) * sizeof(int16)); + + return numSamples; +} + +/** + * Dequeues a note from the pending note list + */ +void PCSpeaker::dequeueNote() { + SpeakerNote note = _pendingNotes.pop(); + + _oscLength = _rate / note.freq; + _oscSamples = 0; + _remainingSamples = (_rate * note.length) / 1000000; + assert((_oscLength > 0) && (_remainingSamples > 0)); +} + +/** + * Support method for generating a square wave + */ +int8 PCSpeaker::generateSquare(uint32 x, uint32 oscLength) { + return (x < (oscLength / 2)) ? 127 : -128; +} + +/*-------------------------------------------------------------------------*/ + +const int tab[16] = { -96, -72, -48, -32, -20, -12, -8, -4, 0, 4, 8, 12, 20, 32, 48, 72 }; + +// The PC timer chip works at a frequency of 1.19318Mhz +#define TIMER_FREQUENCY 1193180 + +SoundManager::SoundManager(Audio::Mixer *mixer) { + _mixer = mixer; + _speakerStream = new PCSpeaker(mixer->getOutputRate()); + _mixer->playStream(Audio::Mixer::kSFXSoundType, &_speakerHandle, + _speakerStream, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +} + +SoundManager::~SoundManager() { + _mixer->stopHandle(_speakerHandle); + delete _speakerStream; + +} + +/** + * Decode music data + */ +void SoundManager::decodeMusic(const byte *PSrc, byte *PDest, int NbreSeg) { + int seed = 128; + int v; + + for (int idx1 = 0; idx1 < (NbreSeg * 2); ++idx1) { + for (int idx2 = 0; idx2 < 64; ++idx2) { + byte srcByte = *PSrc++; + v = tab[srcByte >> 4]; + seed += v; + *PDest++ = seed & 0xff; + + v = tab[srcByte & 0xf]; + seed += v; + *PDest++ = seed & 0xff; + } + } +} + +void SoundManager::litph(tablint &t, int typ, int tempo) { + return; +} + +void SoundManager::playNote(int frequency, int32 length) { + _speakerStream->play(frequency, length); +} + + +void SoundManager::musyc(tablint &tb, int nbseg, int att) { +#ifdef DEBUG + const byte *pSrc = &_vm->_mem[kAdrMusic * 16]; + + // Convert the countdown amount to a tempo rate, and then to note length in microseconds + int tempo = TIMER_FREQUENCY / att; + int length = 1000000 / tempo; + + for (int noteIndex = 0; noteIndex < (nbseg * 16); ++noteIndex) { + int lookupValue = *pSrc++; + int noteCountdown = tb[lookupValue]; + int noteFrequency = TIMER_FREQUENCY / noteCountdown; + + playNote(noteFrequency, length); + } + + // Keep waiting until the song has been finished + while (_speakerStream->isPlaying() && !_vm->shouldQuit()) { + _vm->delay(10); + } +#endif +} + +void SoundManager::setParent(MortevielleEngine *vm) { + _vm = vm; +} +} // End of namespace Mortevielle diff --git a/engines/mortevielle/sound.h b/engines/mortevielle/sound.h new file mode 100644 index 0000000000..a47e8db32e --- /dev/null +++ b/engines/mortevielle/sound.h @@ -0,0 +1,115 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This code is based on original Mortville Manor DOS source code + * Copyright (c) 1987-1989 Lankhor + */ + +#ifndef MORTEVIELLE_SOUND_H +#define MORTEVIELLE_SOUND_H + +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "common/mutex.h" +#include "common/queue.h" + +namespace Mortevielle { +class MortevielleEngine; + +typedef int tablint[256]; + +/** + * Structure used to store pending notes to play + */ +struct SpeakerNote { + int freq; + uint32 length; + + SpeakerNote(int noteFreq, uint32 noteLength) { + freq = noteFreq; + length = noteLength; + } +}; + +/** + * This is a modified PC Speaker class that allows the queueing of an entire song + * sequence one note at a time. + */ +class PCSpeaker : public Audio::AudioStream { +private: + Common::Queue<SpeakerNote> _pendingNotes; + Common::Mutex _mutex; + + int _rate; + uint32 _oscLength; + uint32 _oscSamples; + uint32 _remainingSamples; + uint32 _mixedSamples; + byte _volume; + + void dequeueNote(); +protected: + static int8 generateSquare(uint32 x, uint32 oscLength); +public: + PCSpeaker(int rate = 44100); + ~PCSpeaker(); + + /** Play a note for length microseconds. + */ + void play(int freq, uint32 length); + /** Stop the currently playing sequence */ + void stop(); + /** Adjust the volume. */ + void setVolume(byte volume); + + bool isPlaying() const; + + int readBuffer(int16 *buffer, const int numSamples); + + bool isStereo() const { return false; } + bool endOfData() const { return false; } + bool endOfStream() const { return false; } + int getRate() const { return _rate; } +}; + +class SoundManager { +private: + MortevielleEngine *_vm; + Audio::Mixer *_mixer; + PCSpeaker *_speakerStream; + Audio::SoundHandle _speakerHandle; +public: + SoundManager(Audio::Mixer *mixer); + ~SoundManager(); + + void setParent(MortevielleEngine *vm); + void playNote(int frequency, int32 length); + + void decodeMusic(const byte *PSrc, byte *PDest, int NbreSeg); + void litph(tablint &t, int typ, int tempo); + void musyc(tablint &tb, int nbseg, int att); +}; + +} // End of namespace Mortevielle + +#endif diff --git a/engines/mortevielle/speech.cpp b/engines/mortevielle/speech.cpp new file mode 100644 index 0000000000..68ae3dac3e --- /dev/null +++ b/engines/mortevielle/speech.cpp @@ -0,0 +1,611 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This code is based on original Mortville Manor DOS source code + * Copyright (c) 1987-1989 Lankhor + */ + +#include "mortevielle/mortevielle.h" + +#include "mortevielle/speech.h" +#include "mortevielle/sound.h" + +#include "common/endian.h" +#include "common/file.h" + +namespace Mortevielle { + +const byte _tnocon[364] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +const byte _intcon[26] = {1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0}; +const byte _typcon[26] = {0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3}; +const byte _tabdph[16] = {0, 10, 2, 0, 2, 10, 3, 0, 3, 7, 5, 0, 6, 7, 7, 10}; +const byte _tabdbc[18] = {7, 23, 7, 14, 13, 9, 14, 9, 5, 12, 6, 12, 13, 4, 0, 4, 5, 9}; + +SpeechManager::SpeechManager() { + _typlec = 0; + _phonemeNumb = 0; + + for (int i = 0; i < 3; i++) { + _queue[i]._val = 0; + _queue[i]._code = 0; + _queue[i]._acc = 0; + _queue[i]._freq = 0; + _queue[i]._rep = 0; + } + _noise5Buf = nullptr; +} + +SpeechManager::~SpeechManager() { + free(_noise5Buf); +} + +void SpeechManager::spfrac(int wor) { + _queue[2]._rep = (uint)wor >> 12; + if ((_typlec == 0) && (_queue[2]._code != 9)) + if (((_queue[2]._code > 4) && (_queue[2]._val != 20) && (_queue[2]._rep != 3) && (_queue[2]._rep != 6) && (_queue[2]._rep != 9)) || + ((_queue[2]._code < 5) && ((_queue[2]._val != 19) && (_queue[2]._val != 22) && (_queue[2]._rep != 4) && (_queue[2]._rep != 9)))) { + ++_queue[2]._rep; + } + + _queue[2]._freq = ((uint)wor >> 6) & 7; + _queue[2]._acc = ((uint)wor >> 9) & 7; +} + +void SpeechManager::charg_car(int &currWordNumb) { + int wor = READ_BE_UINT16(&_vm->_mem[(kAdrWord * 16) + currWordNumb]); + int int_ = wor & 0x3f; // 63 + + if ((int_ >= 0) && (int_ <= 13)) { + _queue[2]._val = int_; + _queue[2]._code = 5; + } else if ((int_ >= 14) && (int_ <= 21)) { + _queue[2]._val = int_; + _queue[2]._code = 6; + } else if ((int_ >= 22) && (int_ <= 47)) { + int_ = int_ - 22; + _queue[2]._val = int_; + _queue[2]._code = _typcon[int_]; + } else if ((int_ >= 48) && (int_ <= 56)) { + _queue[2]._val = int_ - 22; + _queue[2]._code = 4; + } else { + switch (int_) { + case 60: + _queue[2]._val = 32; /* " " */ + _queue[2]._code = 9; + break; + case 61: + _queue[2]._val = 46; /* "." */ + _queue[2]._code = 9; + break; + case 62: + _queue[2]._val = 35; /* "#" */ + _queue[2]._code = 9; + default: + break; + } + } + + spfrac(wor); + currWordNumb += 2; +} + + +void SpeechManager::entroct(byte o) { + _vm->_mem[(kAdrTroct * 16) + _ptr_oct] = o; + ++_ptr_oct; +} + +void SpeechManager::veracf(byte b) { + ; +} + +void SpeechManager::cctable(tablint &t) { + float tb[257]; + + tb[0] = 0; + for (int k = 0; k <= 255; ++k) { + tb[k + 1] = _vm->_addFix + tb[k]; + t[255 - k] = abs((int)tb[k] + 1); + } +} + +void SpeechManager::regenbruit() { + int i = kOffsetB3 + 8590; + int j = 0; + do { + _cfiphBuffer[j] = READ_BE_UINT16(&_vm->_mem[(kAdrNoise3 * 16) + i]); + i += 2; + ++j; + } while (i < kOffsetB3 + 8790); +} + +/** + * Load sonmus.mor file + * @remarks Originally called 'charge_son' + */ +void SpeechManager::loadMusicSound() { + Common::File f; + if (!f.open("sonmus.mor")) + error("Missing file - sonmus.mor"); + + free(_vm->_compMusicBuf1); + int size = f.size(); + _vm->_compMusicBuf1 = (byte *)malloc(sizeof(byte) * size); + f.read(_vm->_compMusicBuf1, size); + + _vm->_soundManager.decodeMusic(_vm->_compMusicBuf1, &_vm->_mem[kAdrNoise * 16], size / 128); + f.close(); +} + +/** + * Load phoneme sound file + * @remarks Originally called 'charge_phbruit' + */ +void SpeechManager::loadPhonemeSounds() { + Common::File f; + + if (!f.open("phbrui.mor")) + error("Missing file - phbrui.mor"); + + for (int i = 1; i <= f.size() / 2; ++i) + _cfiphBuffer[i] = f.readUint16BE(); + + f.close(); +} + +/** + * Speech function - Load Noise file + * @remarks Originally called 'charge_bruit' + */ +void SpeechManager::loadNoise() { + Common::File f; + + if (!f.open("bruits")) //Translation: "noise" + error("Missing file - bruits"); + + f.read(&_vm->_mem[kAdrNoise * 16], 250 * 128); // 32000 + for (int i = 0; i < _noise5Size; ++i) + _vm->_mem[(kAdrNoise * 16) + 32000 + i] = _noise5Buf[i]; + f.read(&_vm->_mem[(kAdrNoise1 * 16) + kOffsetB1], 149 * 128); // 19072 + + f.close(); +} + +void SpeechManager::trait_car() { + byte d3; + int d2, i; + + switch (_queue[1]._code) { + case 9: + if (_queue[1]._val != (int)'#') + for (i = 0; i <= _queue[1]._rep; ++i) + entroct(_queue[1]._val); + break; + case 5: + case 6: + if (_queue[1]._code == 6) + d3 = _tabdph[(_queue[1]._val - 14) << 1]; + else + d3 = kNullValue; + if (_queue[0]._code >= 5) { + veracf(_queue[1]._acc); + if (_queue[0]._code == 9) { + entroct(4); + if (d3 == kNullValue) + entroct(_queue[1]._val); + else + entroct(d3); + entroct(22); + } + } + + switch (_queue[1]._rep) { + case 0: + entroct(0); + entroct(_queue[1]._val); + if (d3 == kNullValue) + if (_queue[2]._code == 9) + entroct(2); + else + entroct(4); + else if (_queue[2]._code == 9) + entroct(0); + else + entroct(1); + break; + case 4: + case 5: + case 6: + if (_queue[1]._rep != 4) { + i = _queue[1]._rep - 5; + do { + --i; + entroct(0); + if (d3 == kNullValue) + entroct(_queue[1]._val); + else + entroct(d3); + entroct(3); + } while (i >= 0); + } + if (d3 == kNullValue) { + entroct(4); + entroct(_queue[1]._val); + entroct(0); + } else { + entroct(0); + entroct(_queue[1]._val); + entroct(3); + } + break; + case 7: + case 8: + case 9: + if (_queue[1]._rep != 7) { + i = _queue[1]._rep - 8; + do { + --i; + entroct(0); + if (d3 == kNullValue) + entroct(_queue[1]._val); + else + entroct(d3); + entroct(3); + } while (i >= 0); + } + if (d3 == kNullValue) { + entroct(0); + entroct(_queue[1]._val); + entroct(2); + } else { + entroct(0); + entroct(_queue[1]._val); + entroct(0); + } + break; + case 1: + case 2: + case 3: + if (_queue[1]._rep != 1) { + i = _queue[1]._rep - 2; + do { + --i; + entroct(0); + if (d3 == kNullValue) + entroct(_queue[1]._val); + else + entroct(d3); + entroct(3); + } while (i >= 0); + } + entroct(0); + entroct(_queue[1]._val); + if (_queue[2]._code == 9) + entroct(0); + else + entroct(1); + break; + default: + break; + } // switch c2.rep + break; + + case 2: + case 3: + d3 = _queue[1]._code + 5; // 7 ou 8 => Corresponding vowel + if (_queue[0]._code > 4) { + veracf(_queue[1]._acc); + if (_queue[0]._code == 9) { + entroct(4); + entroct(d3); + entroct(22); + } + } + i = _queue[1]._rep; + assert(i >= 0); + if (i != 0) { + do { + --i; + entroct(0); + entroct(d3); + entroct(3); + } while (i > 0); + } + veracf(_queue[2]._acc); + if (_queue[2]._code == 6) { + entroct(4); + entroct(_tabdph[(_queue[2]._val - 14) << 1]); + entroct(_queue[1]._val); + } else { + entroct(4); + if (_queue[2]._val == 4) + entroct(3); + else + entroct(_queue[2]._val); + entroct(_queue[1]._val); + } + break; + case 0: + case 1: + veracf(_queue[1]._acc); + switch (_queue[2]._code) { + case 2: + d2 = 7; + break; + case 3: + d2 = 8; + break; + case 6: + d2 = _tabdph[(_queue[2]._val - 14) << 1]; + break; + case 5: + d2 = _queue[2]._val; + break; + default: + d2 = 10; + break; + } // switch c3._code + d2 = (d2 * 26) + _queue[1]._val; + if (_tnocon[d2] == 0) + d3 = 2; + else + d3 = 6; + if (_queue[1]._rep >= 5) { + _queue[1]._rep -= 5; + d3 = 8 - d3; // Swap 2 and 6 + } + if (_queue[1]._code == 0) { + i = _queue[1]._rep; + if (i != 0) { + do { + --i; + entroct(d3); + entroct(_queue[1]._val); + entroct(3); + } while (i > 0); + } + entroct(d3); + entroct(_queue[1]._val); + entroct(4); + } else { + entroct(d3); + entroct(_queue[1]._val); + entroct(3); + i = _queue[1]._rep; + if (i != 0) { + do { + --i; + entroct(d3); + entroct(_queue[1]._val); + entroct(4); + } while (i > 0); + } + } + if (_queue[2]._code == 9) { + entroct(d3); + entroct(_queue[1]._val); + entroct(5); + } else if ((_queue[2]._code != 0) && (_queue[2]._code != 1) && (_queue[2]._code != 4)) { + veracf(_queue[2]._acc); + switch (_queue[2]._code) { + case 3: + d2 = 8; + break; + case 6: + d2 = _tabdph[(_queue[2]._val - 14) << 1]; + break; + case 5: + d2 = _queue[2]._val; + break; + default: + d2 = 7; + break; + } // switch c3._code + if (d2 == 4) + d2 = 3; + + if (_intcon[_queue[1]._val] != 0) + ++_queue[1]._val; + + if ((_queue[1]._val == 17) || (_queue[1]._val == 18)) + _queue[1]._val = 16; + + entroct(4); + entroct(d2); + entroct(_queue[1]._val); + } + + break; + case 4: + veracf(_queue[1]._acc); + i = _queue[1]._rep; + if (i != 0) { + do { + --i; + entroct(2); + entroct(_queue[1]._val); + entroct(3); + } while (i > 0); + } + entroct(2); + entroct(_queue[1]._val); + entroct(4); + if (_queue[2]._code == 9) { + entroct(2); + entroct(_queue[1]._val); + entroct(5); + } else if ((_queue[2]._code != 0) && (_queue[2]._code != 1) && (_queue[2]._code != 4)) { + veracf(_queue[2]._acc); + switch (_queue[2]._code) { + case 3: + d2 = 8; + break; + case 6: + d2 = _tabdph[(_queue[2]._val - 14) << 1]; + break; + case 5: + d2 = _queue[2]._val; + break; + default: + d2 = 7; + break; + } // switch c3._code + + if (d2 == 4) + d2 = 3; + + if (_intcon[_queue[1]._val] != 0) + ++_queue[1]._val; + + entroct(4); + entroct(d2); + entroct(_tabdbc[((_queue[1]._val - 26) << 1) + 1]); + } + + break; + default: + break; + } // switch c2.code +} + +/** + * Make the queue evolve by 1 value + * @remarks Originally called 'rot_chariot' + */ +void SpeechManager::moveQueue() { + _queue[0] = _queue[1]; + _queue[1] = _queue[2]; + _queue[2]._val = 32; + _queue[2]._code = 9; +} + +/** + * initialize the queue + * @remarks Originally called 'init_chariot' + */ +void SpeechManager::initQueue() { + _queue[2]._rep = 0; + _queue[2]._freq = 0; + _queue[2]._acc = 0; + moveQueue(); + moveQueue(); +} + +/** + * Handle a phoneme + * @remarks Originally called 'trait_ph' + */ +void SpeechManager::handlePhoneme() { + const uint16 deca[3] = {300, 30, 40}; + + uint16 startPos = _cfiphBuffer[_phonemeNumb - 1] + deca[_typlec]; + uint16 endPos = _cfiphBuffer[_phonemeNumb] + deca[_typlec]; + int wordCount = endPos - startPos; + + startPos /= 2; + endPos /= 2; + for (int i = startPos, currWord = 0; i < endPos; i++, currWord += 2) + WRITE_BE_UINT16(&_vm->_mem[(kAdrWord * 16) + currWord], _cfiphBuffer[i]); + + _ptr_oct = 0; + int currWord = 0; + initQueue(); + + do { + moveQueue(); + charg_car(currWord); + trait_car(); + } while (currWord < wordCount); + + moveQueue(); + trait_car(); + entroct((int)'#'); +} + +/** + * Start speech + * @remarks Originally called 'parole' + */ +void SpeechManager::startSpeech(int rep, int ht, int typ) { + uint16 savph[501]; + int tempo; + + if (_vm->_soundOff) + return; + + _phonemeNumb = rep; + int haut = ht; + _typlec = typ; + if (_typlec != 0) { + for (int i = 0; i <= 500; ++i) + savph[i] = _cfiphBuffer[i]; + tempo = kTempoNoise; + } else if (haut > 5) + tempo = kTempoF; + else + tempo = kTempoM; + _vm->_addFix = (float)((tempo - 8)) / 256; + cctable(_tbi); + switch (typ) { + case 1: + loadNoise(); + regenbruit(); + break; + case 2: + loadMusicSound(); + loadPhonemeSounds(); + break; + default: + break; + } + handlePhoneme(); + _vm->_soundManager.litph(_tbi, typ, tempo); + if (_typlec != 0) + for (int i = 0; i <= 500; ++i) { + _cfiphBuffer[i] = savph[i]; + _mlec = _typlec; + } + _vm->setPal(_vm->_numpal); +} + +void SpeechManager::setParent(MortevielleEngine *vm) { + _vm = vm; +} +} // End of namespace Mortevielle diff --git a/engines/mortevielle/speech.h b/engines/mortevielle/speech.h new file mode 100644 index 0000000000..c3c4c32942 --- /dev/null +++ b/engines/mortevielle/speech.h @@ -0,0 +1,106 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This code is based on original Mortville Manor DOS source code + * Copyright (c) 1987-1989 Lankhor + */ + +#ifndef MORTEVIELLE_SPEECH_H +#define MORTEVIELLE_SPEECH_H + +#include "mortevielle/sound.h" + +#include "common/scummsys.h" + +namespace Mortevielle { + +const int kAdrNoise = 0x5cb0;/*2C00;*/ +const int kAdrNoise1 = 0x6924; +const int kAdrNoise3 = 0x6ba6;/*3AF6;*/ +const int kAdrTroct = 0x406b; +const int kAdrWord = 0x4000; +const int kOffsetB1 = 6; +const int kOffsetB3 = 6; + +const float kfreq0 = 1.19318e6; +const int kNullValue = 255; +const int kTempoMusic = 71; +const int kTempoNoise = 78; +const int kTempoF = 80; +const int kTempoM = 89; + +// Useless constants +//const int segdon = 0x6c00; +//const int adbruit2 = 0x6b30;/*3A80;*/ +//const int adson2 = 0x60b0;/*3000;*/ +//const int seg_syst = 0x6fed; +//const int offsetb2 = 4; + +struct SpeechQueue { + int _val; + int _code; + int _acc; + int _freq; + int _rep; +}; + +class SpeechManager { +private: + MortevielleEngine *_vm; + + int _typlec; + int _phonemeNumb; + + SpeechQueue _queue[3]; + int _ptr_oct; + +public: + uint16 *_cfiphBuffer; + int _tbi[256]; + int _mlec; + byte *_noise5Buf; + int _noise5Size; + + SpeechManager(); + ~SpeechManager(); + void setParent(MortevielleEngine *vm); + void spfrac(int wor); + void charg_car(int &currWordNumb); + void entroct(byte o); + void veracf(byte b); + void cctable(tablint &t); + void regenbruit(); + void loadMusicSound(); + void loadPhonemeSounds(); + void loadNoise(); + void trait_car(); + + void moveQueue(); + void initQueue(); + void handlePhoneme(); + void startSpeech(int rep, int ht, int typ); +}; + +} // End of namespace Mortevielle + +#endif diff --git a/engines/mortevielle/utils.cpp b/engines/mortevielle/utils.cpp new file mode 100644 index 0000000000..2316d1e9fd --- /dev/null +++ b/engines/mortevielle/utils.cpp @@ -0,0 +1,3497 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * This code is based on original Mortville Manor DOS source code + * Copyright (c) 1987-1989 Lankhor + */ + +#include "mortevielle/mortevielle.h" + +#include "mortevielle/dialogs.h" +#include "mortevielle/menu.h" +#include "mortevielle/mouse.h" +#include "mortevielle/outtext.h" +#include "mortevielle/speech.h" + +#include "common/scummsys.h" +#include "graphics/cursorman.h" + +namespace Mortevielle { + +/** + * Check is a key was pressed + * It also delays the engine and check if the screen has to be updated + * @remarks Originally called 'keypressed' + */ +bool MortevielleEngine::keyPressed() { + // Check for any pending key presses + handleEvents(); + + // Check if it's time to draw the next frame + if (g_system->getMillis() > (_lastGameFrame + GAME_FRAME_DELAY)) { + _lastGameFrame = g_system->getMillis(); + + _screenSurface.updateScreen(); + + _debugger.onFrame(); + } + + // Delay briefly to keep CPU usage down + g_system->delayMillis(5); + + // Return if there are any pending key presses + return !_keypresses.empty(); +} + +/** + * Wait for a keypress + * @remarks Originally called 'get_ch' + */ +int MortevielleEngine::getChar() { + // If there isn't any pending keypress, wait until there is + while (!shouldQuit() && _keypresses.empty()) { + keyPressed(); + } + + // Return the top keypress + return shouldQuit() ? 0 : _keypresses.pop(); +} + +/** + * Handle pending events + * @remarks Since the ScummVM screen surface is double height to handle 640x200 using 640x400, + * the mouse Y position is divided by 2 to keep the game thinking the Y goes from 0 - 199 + */ +bool MortevielleEngine::handleEvents() { + Common::Event event; + if (!g_system->getEventManager()->pollEvent(event)) + return false; + + switch (event.type) { + case Common::EVENT_LBUTTONDOWN: + case Common::EVENT_LBUTTONUP: + case Common::EVENT_MOUSEMOVE: + _mousePos = Common::Point(event.mouse.x, event.mouse.y / 2); + _mouse._pos.x = event.mouse.x; + _mouse._pos.y = event.mouse.y / 2; + + if (event.type == Common::EVENT_LBUTTONDOWN) + _mouseClick = true; + else if (event.type == Common::EVENT_LBUTTONUP) + _mouseClick = false; + + break; + case Common::EVENT_KEYDOWN: + addKeypress(event); + break; + default: + break; + } + + return true; +} + +/** + * Add the specified key to the pending keypress stack + */ +void MortevielleEngine::addKeypress(Common::Event &evt) { + // Character to add + char ch = evt.kbd.ascii; + + // Check for debugger + if ((evt.kbd.keycode == Common::KEYCODE_d) && (evt.kbd.flags & Common::KBD_CTRL)) { + // Attach to the debugger + _debugger.attach(); + _debugger.onFrame(); + } else if ((evt.kbd.keycode >= Common::KEYCODE_a) && (evt.kbd.keycode <= Common::KEYCODE_z)) { + // Handle alphabetic keys + if (evt.kbd.hasFlags(Common::KBD_CTRL)) + ch = evt.kbd.keycode - Common::KEYCODE_a + 1; + else + ch = evt.kbd.keycode - Common::KEYCODE_a + 'A'; + } else if ((evt.kbd.keycode >= Common::KEYCODE_F1) && (evt.kbd.keycode <= Common::KEYCODE_F12)) { + // Handle function keys + ch = 59 + evt.kbd.keycode - Common::KEYCODE_F1; + } else { + // Series of special cases + switch (evt.kbd.keycode) { + case Common::KEYCODE_KP4: + case Common::KEYCODE_LEFT: + ch = '4'; + break; + case Common::KEYCODE_KP2: + case Common::KEYCODE_DOWN: + ch = '2'; + break; + case Common::KEYCODE_KP6: + case Common::KEYCODE_RIGHT: + ch = '6'; + break; + case Common::KEYCODE_KP8: + case Common::KEYCODE_UP: + ch = '8'; + break; + case Common::KEYCODE_KP7: + ch = '7'; + break; + case Common::KEYCODE_KP1: + ch = '1'; + break; + case Common::KEYCODE_KP9: + ch = '9'; + break; + case Common::KEYCODE_KP3: + ch = '3'; + break; + case Common::KEYCODE_KP5: + ch = '5'; + break; + case Common::KEYCODE_RETURN: + ch = '\13'; + break; + case Common::KEYCODE_ESCAPE: + ch = '\33'; + break; + default: + break; + } + } + + if (ch != 0) + _keypresses.push(ch); +} + + +static const byte CURSOR_ARROW_DATA[16 * 16] = { + 0x0f, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0f, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0f, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0f, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0f, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0f, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0f, 0x00, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0f, 0x0f, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +/** + * Initialize the mouse + */ +void MortevielleEngine::initMouse() { + CursorMan.replaceCursor(CURSOR_ARROW_DATA, 16, 16, 0, 0, 0xff); + CursorMan.showMouse(true); + + _mouse.initMouse(); +} + +/** + * Sets the mouse position + * @remarks Since the ScummVM screen surface is double height to handle 640x200 using 640x400, + * the mouse Y position is doubled to convert from 0-199 to 0-399 + */ +void MortevielleEngine::setMousePos(const Common::Point &pt) { + // Adjust the passed position from simulated 640x200 to 640x400 co-ordinates + Common::Point newPoint(pt.x, (pt.y == 199) ? 399 : pt.y * 2); + + if (newPoint != _mousePos) + // Warp the mouse to the new position + g_system->warpMouse(newPoint.x, newPoint.y); + + // Save the new position + _mousePos = newPoint; +} + +/** + * Delay by a given amount + */ +void MortevielleEngine::delay(int amount) { + uint32 endTime = g_system->getMillis() + amount; + + while (g_system->getMillis() < endTime) { + if (g_system->getMillis() > (_lastGameFrame + GAME_FRAME_DELAY)) { + _lastGameFrame = g_system->getMillis(); + _screenSurface.updateScreen(); + + _debugger.onFrame(); + } + + g_system->delayMillis(10); + } +} + +/** + * Waits for the user to select an action, and then handles it + * @remarks Originally called tecran + */ +void MortevielleEngine::handleAction() { + const int lim = 20000; + int temps = 0; + char inkey = '\0'; + bool funct = false; + + clearVerbBar(); + + bool handledOpcodeFl = false; + _controlMenu = 0; + if (!_keyPressedEsc) { + _menu.drawMenu(); + _menu._menuDisplayed = true; + temps = 0; + _key = 0; + funct = false; + inkey = '.'; + + _inMainGameLoop = true; + do { + _menu.updateMenu(); + prepareRoom(); + _mouse.moveMouse(funct, inkey); + if (shouldQuit()) + return; + ++temps; + } while (!((_menu._menuSelected) || (temps > lim) || (funct) || (_anyone))); + _inMainGameLoop = false; + + _menu.eraseMenu(); + _menu._menuDisplayed = false; + if ((inkey == '\1') || (inkey == '\3') || (inkey == '\5') || (inkey == '\7') || (inkey == '\11')) { + changeGraphicalDevice((uint)((int)inkey - 1) >> 1); + return; + } + if (_menu._menuSelected && (_currMenu == MENU_SAVE)) { + Common::String saveName = Common::String::format("Savegame #%d", _currAction & 15); + _savegameManager.saveGame(_currAction & 15, saveName); + } + if (_menu._menuSelected && (_currMenu == MENU_LOAD)) + _savegameManager.loadGame((_currAction & 15) - 1); + if (inkey == '\103') { /* F9 */ + temps = _dialogManager.show(_hintPctMessage, 1); + return; + } else if (inkey == '\77') { + if ((_menuOpcode != OPCODE_NONE) && ((_currMenu == MENU_ACTION) || (_currMenu == MENU_SELF))) { + _currAction = _menuOpcode; + displayTextInVerbBar(getEngineString(S_IDEM)); + } else + return; + } else if (inkey == '\104') { + if ((_x != 0) && (_y != 0)) + _num = 9999; + return; + } + } + if (inkey == '\73') { + _quitGame = true; + hourToChar(); + } else { + if ((funct) && (inkey != '\77')) + return; + if (temps > lim) { + handleDescriptionText(2, 141); + if (_num == 9999) + _num = 0; + } else { + _menuOpcode = _currMenu; + if ((_currMenu == MENU_ACTION) || (_currMenu == MENU_SELF)) + _menuOpcode = _currAction; + if (!_anyone) { + if ((_heroSearching) || (_obpart)) { + if (_mouse._pos.y < 12) + return; + + if ((_currAction == OPCODE_SOUND) || (_currAction == OPCODE_LIFT)) { + handledOpcodeFl = true; + if ((_currAction == OPCODE_LIFT) || (_obpart)) { + endSearch(); + _caff = _coreVar._currPlace; + _crep = 998; + } else + tsuiv(); + mennor(); + } + } + } + do { + if (!handledOpcodeFl) + handleOpcode(); + + if ((_controlMenu == 0) && (! _loseGame) && (! _endGame)) { + _text.taffich(); + if (_destinationOk) { + _destinationOk = false; + drawPicture(); + } + if ((!_syn) || (_col)) + handleDescriptionText(2, _crep); + } + } while (_syn); + if (_controlMenu != 0) + displayControlMenu(); + } + } +} + +/** + * Engine function - Init Places + * @remarks Originally called 'init_lieu' + */ +void MortevielleEngine::loadPlaces() { + Common::File f; + + if (!f.open("MXX.mor")) + if (!f.open("MFXX.mor")) + error("Missing file - MXX.mor"); + + for (int i = 0; i < 7; ++i) { + for (int j = 0; j < 25; ++j) + _destinationArray[i][j] = f.readByte(); + } + + f.close(); +} + +/** + * Set Text Color + * @remarks Originally called 'text_color' + */ +void MortevielleEngine::setTextColor(int col) { + _textColor = col; +} + +/** + * Prepare screen - Type 1! + * @remarks Originally called 'ecrf1' + */ +void MortevielleEngine::prepareScreenType1() { + // Large drawing + _screenSurface.drawBox(0, 11, 512, 164, 15); +} + +/** + * Prepare room - Type 2! + * @remarks Originally called 'ecrf2' + */ +void MortevielleEngine::prepareScreenType2() { + setTextColor(5); +} + +/** + * Prepare room - Type 3! + * @remarks Originally called 'ecrf7' + */ +void MortevielleEngine::prepareScreenType3() { + setTextColor(4); +} + +/** + * Engine function - Update hour + * @remarks Originally called 'calch' + */ +void MortevielleEngine::updateHour(int &day, int &hour, int &minute) { + int newHour = readclock(); + int th = _currentHourCount + ((newHour - _currentDayHour) / _inGameHourDuration); + minute = ((th % 2) + _currHalfHour) * 30; + hour = ((uint)th >> 1) + _currHour; + if (minute == 60) { + minute = 0; + ++hour; + } + day = (hour / 24) + _currDay; + hour = hour - ((day - _currDay) * 24); +} + +/** + * Engine function - Convert character index to bit index + * @remarks Originally called 'conv' + */ +int MortevielleEngine::convertCharacterIndexToBitIndex(int characterIndex) { + return 128 >> (characterIndex - 1); +} + +/** + * Engine function - Convert bit index to character index + * @remarks Originally called 'tip' + */ +int MortevielleEngine::convertBitIndexToCharacterIndex(int bitIndex) { + int retVal = 0; + + if (bitIndex == 128) + retVal = 1; + else if (bitIndex == 64) + retVal = 2; + else if (bitIndex == 32) + retVal = 3; + else if (bitIndex == 16) + retVal = 4; + else if (bitIndex == 8) + retVal = 5; + else if (bitIndex == 4) + retVal = 6; + else if (bitIndex == 2) + retVal = 7; + else if (bitIndex == 1) + retVal = 8; + + return retVal; +} + +/** + * Engine function - Reset presence in other rooms + * @remarks Originally called 't5' + */ +void MortevielleEngine::resetPresenceInRooms(int roomId) { + if (roomId == DINING_ROOM) + _blo = false; + + if (roomId != GREEN_ROOM) { + _roomPresenceLuc = false; + _roomPresenceIda = false; + } + + if (roomId != PURPLE_ROOM) + _purpleRoomPresenceLeo = false; + + if (roomId != DARKBLUE_ROOM) { + _roomPresenceGuy = false; + _roomPresenceEva = false; + } + + if (roomId != BLUE_ROOM) + _roomPresenceMax = false; + if (roomId != RED_ROOM) + _roomPresenceBob = false; + if (roomId != GREEN_ROOM2) + _roomPresencePat = false; + if (roomId != TOILETS) + _toiletsPresenceBobMax = false; + if (roomId != BATHROOM) + _bathRoomPresenceBobMax = false; + if (roomId != ROOM9) + _room9PresenceLeo = false; +} + +/** + * Engine function - Show the people present in the given room + * @remarks Originally called 'affper' + */ +void MortevielleEngine::showPeoplePresent(int bitIndex) { + int xp = 580 - (_screenSurface.getStringWidth("LEO") / 2); + + for (int i = 1; i <= 8; ++i) + _menu.disableMenuItem(_menu._discussMenu[i]._menuId, _menu._discussMenu[i]._actionId); + + clearUpperRightPart(); + if ((bitIndex & 128) == 128) { + _screenSurface.putxy(xp, 24); + _screenSurface.drawString("LEO", 4); + _menu.enableMenuItem(_menu._discussMenu[1]._menuId, _menu._discussMenu[1]._actionId); + } + if ((bitIndex & 64) == 64) { + _screenSurface.putxy(xp, 32); + _screenSurface.drawString("PAT", 4); + _menu.enableMenuItem(_menu._discussMenu[2]._menuId, _menu._discussMenu[2]._actionId); + } + if ((bitIndex & 32) == 32) { + _screenSurface.putxy(xp, 40); + _screenSurface.drawString("GUY", 4); + _menu.enableMenuItem(_menu._discussMenu[3]._menuId, _menu._discussMenu[3]._actionId); + } + if ((bitIndex & 16) == 16) { + _screenSurface.putxy(xp, 48); + _screenSurface.drawString("EVA", 4); + _menu.enableMenuItem(_menu._discussMenu[4]._menuId, _menu._discussMenu[4]._actionId); + } + if ((bitIndex & 8) == 8) { + _screenSurface.putxy(xp, 56); + _screenSurface.drawString("BOB", 4); + _menu.enableMenuItem(_menu._discussMenu[5]._menuId, _menu._discussMenu[5]._actionId); + } + if ((bitIndex & 4) == 4) { + _screenSurface.putxy(xp, 64); + _screenSurface.drawString("LUC", 4); + _menu.enableMenuItem(_menu._discussMenu[6]._menuId, _menu._discussMenu[6]._actionId); + } + if ((bitIndex & 2) == 2) { + _screenSurface.putxy(xp, 72); + _screenSurface.drawString("IDA", 4); + _menu.enableMenuItem(_menu._discussMenu[7]._menuId, _menu._discussMenu[7]._actionId); + } + if ((bitIndex & 1) == 1) { + _screenSurface.putxy(xp, 80); + _screenSurface.drawString("MAX", 4); + _menu.enableMenuItem(_menu._discussMenu[8]._menuId, _menu._discussMenu[8]._actionId); + } + _currBitIndex = bitIndex; +} + +/** + * Engine function - Select random characters + * @remarks Originally called 'choix' + */ +int MortevielleEngine::selectCharacters(int min, int max) { + bool invertSelection = false; + int rand = getRandomNumber(min, max); + + if (rand > 4) { + rand = 8 - rand; + invertSelection = true; + } + + int i = 0; + int retVal = 0; + while (i < rand) { + int charIndex = getRandomNumber(1, 8); + int charBitIndex = convertCharacterIndexToBitIndex(charIndex); + if ((retVal & charBitIndex) != charBitIndex) { + ++i; + retVal |= charBitIndex; + } + } + if (invertSelection) + retVal = 255 - retVal; + + return retVal; +} + +/** + * Engine function - Get Presence Statistics - Green Room + * @remarks Originally called 'cpl1' + */ +int MortevielleEngine::getPresenceStatsGreenRoom() { + int day, hour, minute; + int retVal = 0; + + updateHour(day, hour, minute); + // The original uses an || instead of an &&, resulting + // in an always true condition. Based on the other tests, + // and on other scenes, we use an && instead. + if ((hour > 7) && (hour < 11)) + retVal = 25; + else if ((hour > 10) && (hour < 14)) + retVal = 35; + else if ((hour > 13) && (hour < 16)) + retVal = 50; + else if ((hour > 15) && (hour < 18)) + retVal = 5; + else if ((hour > 17) && (hour < 22)) + retVal = 35; + else if ((hour > 21) && (hour < 24)) + retVal = 50; + else if ((hour >= 0) && (hour < 8)) + retVal = 70; + + _menu.updateMenu(); + + return retVal; +} +/** + * Engine function - Get Presence Statistics - Purple Room + * @remarks Originally called 'cpl2' + */ +int MortevielleEngine::getPresenceStatsPurpleRoom() { + int day, hour, minute; + int retVal = 0; + + updateHour(day, hour, minute); + if ((hour > 7) && (hour < 11)) + retVal = -2; + else if (hour == 11) + retVal = 100; + else if ((hour > 11) && (hour < 23)) + retVal = 10; + else if (hour == 23) + retVal = 20; + else if ((hour >= 0) && (hour < 8)) + retVal = 50; + + return retVal; +} + +/** + * Engine function - Get Presence Statistics - Toilets + * @remarks Originally called 'cpl3' + */ +int MortevielleEngine::getPresenceStatsToilets() { + int day, hour, minute; + int retVal = 0; + + updateHour(day, hour, minute); + if (((hour > 8) && (hour < 10)) || ((hour > 19) && (hour < 24))) + retVal = 34; + else if (((hour > 9) && (hour < 20)) || ((hour >= 0) && (hour < 9))) + retVal = 0; + + return retVal; +} + +/** + * Engine function - Get Presence Statistics - Blue Room + * @remarks Originally called 'cpl5' + */ +int MortevielleEngine::getPresenceStatsBlueRoom() { + int day, hour, minute; + int retVal = 0; + + updateHour(day, hour, minute); + if ((hour > 6) && (hour < 10)) + retVal = 0; + else if (hour == 10) + retVal = 100; + else if ((hour > 10) && (hour < 24)) + retVal = 15; + else if ((hour >= 0) && (hour < 7)) + retVal = 50; + + return retVal; +} + +/** + * Engine function - Get Presence Statistics - Red Room + * @remarks Originally called 'cpl6' + */ +int MortevielleEngine::getPresenceStatsRedRoom() { + int day, hour, minute; + int retVal = 0; + + updateHour(day, hour, minute); + if (((hour > 7) && (hour < 13)) || ((hour > 17) && (hour < 20))) + retVal = -2; + else if (((hour > 12) && (hour < 17)) || ((hour > 19) && (hour < 24))) + retVal = 35; + else if (hour == 17) + retVal = 100; + else if ((hour >= 0) && (hour < 8)) + retVal = 60; + + return retVal; +} + +/** + * Shows the "you are alone" message in the status area + * on the right hand side of the screen + * @remarks Originally called 'person' + */ +void MortevielleEngine::displayAloneText() { + for (int i = 1; i <= 8; ++i) + _menu.disableMenuItem(_menu._discussMenu[i]._menuId, _menu._discussMenu[i]._actionId); + + Common::String sYou = getEngineString(S_YOU); + Common::String sAre = getEngineString(S_ARE); + Common::String sAlone = getEngineString(S_ALONE); + + clearUpperRightPart(); + _screenSurface.putxy(580 - (_screenSurface.getStringWidth(sYou) / 2), 30); + _screenSurface.drawString(sYou, 4); + _screenSurface.putxy(580 - (_screenSurface.getStringWidth(sAre) / 2), 50); + _screenSurface.drawString(sAre, 4); + _screenSurface.putxy(580 - (_screenSurface.getStringWidth(sAlone) / 2), 70); + _screenSurface.drawString(sAlone, 4); + + _currBitIndex = 0; +} + +/** + * Engine function - Get Presence Statistics - Room Bureau + * @remarks Originally called 'cpl10' + */ +int MortevielleEngine::getPresenceStatsDiningRoom(int &hour) { + int day, minute; + + int retVal = 0; + updateHour(day, hour, minute); + if (((hour > 7) && (hour < 11)) || ((hour > 11) && (hour < 14)) || ((hour > 18) && (hour < 21))) + retVal = 100; + else if ((hour == 11) || ((hour > 20) && (hour < 24))) + retVal = 45; + else if (((hour > 13) && (hour < 17)) || (hour == 18)) + retVal = 35; + else if (hour == 17) + retVal = 60; + else if ((hour >= 0) && (hour < 8)) + retVal = 5; + + return retVal; +} + +/** + * Engine function - Get Presence Statistics - Room Bureau + * @remarks Originally called 'cpl11' + */ +int MortevielleEngine::getPresenceStatsBureau(int &hour) { + int day, minute; + int retVal = 0; + + updateHour(day, hour, minute); + if (((hour > 8) && (hour < 12)) || ((hour > 20) && (hour < 24))) + retVal = 25; + else if (((hour > 11) && (hour < 14)) || ((hour > 18) && (hour < 21))) + retVal = 5; + else if ((hour > 13) && (hour < 17)) + retVal = 55; + else if ((hour > 16) && (hour < 19)) + retVal = 45; + else if ((hour >= 0) && (hour < 9)) + retVal = 0; + + return retVal; +} + +/** + * Engine function - Get Presence Statistics - Room Kitchen + * @remarks Originally called 'cpl12' + */ +int MortevielleEngine::getPresenceStatsKitchen() { + int day, hour, minute; + int retVal = 0; + + updateHour(day, hour, minute); + if (((hour > 8) && (hour < 15)) || ((hour > 16) && (hour < 22))) + retVal = 55; + else if (((hour > 14) && (hour < 17)) || ((hour > 21) && (hour < 24))) + retVal = 25; + else if ((hour >= 0) && (hour < 5)) + retVal = 0; + else if ((hour > 4) && (hour < 9)) + retVal = 15; + + return retVal; +} + +/** + * Engine function - Get Presence Statistics - Room Attic + * @remarks Originally called 'cpl13' + */ +int MortevielleEngine::getPresenceStatsAttic() { + return 0; +} + +/** + * Engine function - Get Presence Statistics - Room Landing + * @remarks Originally called 'cpl15' + */ +int MortevielleEngine::getPresenceStatsLanding() { + int day, hour, minute; + int retVal = 0; + + updateHour(day, hour, minute); + if ((hour > 7) && (hour < 12)) + retVal = 25; + else if ((hour > 11) && (hour < 14)) + retVal = 0; + else if ((hour > 13) && (hour < 18)) + retVal = 10; + else if ((hour > 17) && (hour < 20)) + retVal = 55; + else if ((hour > 19) && (hour < 22)) + retVal = 5; + else if ((hour > 21) && (hour < 24)) + retVal = 15; + else if ((hour >= 0) && (hour < 8)) + retVal = -15; + + return retVal; +} + +/** + * Engine function - Get Presence Statistics - Room Chapel + * @remarks Originally called 'cpl20' + */ +int MortevielleEngine::getPresenceStatsChapel(int &hour) { + int day, minute; + int retVal = 0; + + updateHour(day, hour, minute); + if (hour == 10) + retVal = 65; + else if ((hour > 10) && (hour < 21)) + retVal = 5; + else if ((hour > 20) && (hour < 24)) + retVal = -15; + else if ((hour >= 0) && (hour < 5)) + retVal = -300; + else if ((hour > 4) && (hour < 10)) + retVal = -5; + + return retVal; +} + +/** + * Engine function - Check who is in the Green Room + * @remarks Originally called 'quelq1' + */ +void MortevielleEngine::setPresenceGreenRoom(int roomId) { + int rand = getRandomNumber(1, 2); + if (roomId == GREEN_ROOM) { + if (rand == 1) + _roomPresenceLuc = true; + else + _roomPresenceIda = true; + } else if (roomId == DARKBLUE_ROOM) { + if (rand == 1) + _roomPresenceGuy = true; + else + _roomPresenceEva = true; + } + + _currBitIndex = 10; +} + +/** + * Engine function - Check who is in the Purple Room + * @remarks Originally called 'quelq2' + */ +void MortevielleEngine::setPresencePurpleRoom() { + if (_place == PURPLE_ROOM) + _purpleRoomPresenceLeo = true; + else + _room9PresenceLeo = true; + + _currBitIndex = 10; +} + +/** + * Engine function - Check who is in the Blue Room + * @remarks Originally called 'quelq5' + */ +void MortevielleEngine::setPresenceBlueRoom() { + _roomPresenceMax = true; + _currBitIndex = 10; +} + +/** + * Engine function - Check who is in the Red Room + * @remarks Originally called 'quelq6' + */ +void MortevielleEngine::setPresenceRedRoom(int roomId) { + if (roomId == RED_ROOM) + _roomPresenceBob = true; + else if (roomId == GREEN_ROOM2) + _roomPresencePat = true; + + _currBitIndex = 10; +} + +/** + * Engine function - Check who is in the Dining Room + * @remarks Originally called 'quelq10' + */ +int MortevielleEngine::setPresenceDiningRoom(int hour) { + int retVal = 0; + + if ((hour >= 0) && (hour < 8)) + retVal = checkLeoMaxRandomPresence(); + else { + int min = 0, max = 0; + if ((hour > 7) && (hour < 10)) { + min = 5; + max = 7; + } else if ((hour > 9) && (hour < 12)) { + min = 1; + max = 4; + } else if (((hour > 11) && (hour < 15)) || ((hour > 18) && (hour < 21))) { + min = 6; + max = 8; + } else if (((hour > 14) && (hour < 19)) || ((hour > 20) && (hour < 24))) { + min = 1; + max = 5; + } + retVal = selectCharacters(min, max); + } + showPeoplePresent(retVal); + + return retVal; +} + +/** + * Engine function - Check who is in the Bureau + * @remarks Originally called 'quelq11' + */ +int MortevielleEngine::setPresenceBureau(int hour) { + int retVal = 0; + + if ((hour >= 0) && (hour < 8)) + retVal = checkLeoMaxRandomPresence(); + else { + int min = 0, max = 0; + if (((hour > 7) && (hour < 10)) || ((hour > 20) && (hour < 24))) { + min = 1; + max = 3; + } else if (((hour > 9) && (hour < 12)) || ((hour > 13) && (hour < 19))) { + min = 1; + max = 4; + } else if (((hour > 11) && (hour < 14)) || ((hour > 18) && (hour < 21))) { + min = 1; + max = 2; + } + retVal = selectCharacters(min, max); + } + showPeoplePresent(retVal); + + return retVal; +} + +/** + * Engine function - Check who is in the Kitchen + * @remarks Originally called 'quelq12' + */ +int MortevielleEngine::setPresenceKitchen() { + int retVal = checkLeoMaxRandomPresence(); + showPeoplePresent(retVal); + + return retVal; +} + +/** + * Engine function - Check who is in the Landing + * @remarks Originally called 'quelq15' + */ +int MortevielleEngine::setPresenceLanding() { + bool test = false; + int rand = 0; + do { + rand = getRandomNumber(1, 8); + test = (((rand == 1) && (_purpleRoomPresenceLeo || _room9PresenceLeo)) || + ((rand == 2) && _roomPresencePat) || + ((rand == 3) && _roomPresenceGuy) || + ((rand == 4) && _roomPresenceEva) || + ((rand == 5) && _roomPresenceBob) || + ((rand == 6) && _roomPresenceLuc) || + ((rand == 7) && _roomPresenceIda) || + ((rand == 8) && _roomPresenceMax)); + } while (test); + + int retVal = convertCharacterIndexToBitIndex(rand); + showPeoplePresent(retVal); + + return retVal; +} + +/** + * Engine function - Check who is in the chapel + * @remarks Originally called 'quelq20' + */ +int MortevielleEngine::setPresenceChapel(int hour) { + int retVal = 0; + + if (((hour >= 0) && (hour < 10)) || ((hour > 18) && (hour < 24))) + retVal = checkLeoMaxRandomPresence(); + else { + int min = 0, max = 0; + if ((hour > 9) && (hour < 12)) { + min = 3; + max = 7; + } else if ((hour > 11) && (hour < 18)) { + min = 1; + max = 2; + } else if (hour == 18) { + min = 2; + max = 4; + } + retVal = selectCharacters(min, max); + } + showPeoplePresent(retVal); + + return retVal; +} + +/** + * Engine function - Get the answer after you known a door + * @remarks Originally called 'frap' + */ +void MortevielleEngine::getKnockAnswer() { + int day, hour, minute; + + updateHour(day, hour, minute); + if ((hour >= 0) && (hour < 8)) + _crep = 190; + else { + if (getRandomNumber(1, 100) > 70) + _crep = 190; + else + _crep = 147; + } +} + +/** + * Engine function - Get Room Presence Bit Index + * @remarks Originally called 'nouvp' + */ +int MortevielleEngine::getPresenceBitIndex(int roomId) { + int bitIndex = 0; + if (roomId == GREEN_ROOM) { + if (_roomPresenceLuc) + bitIndex = 4; // LUC + if (_roomPresenceIda) + bitIndex = 2; // IDA + } else if ( ((roomId == PURPLE_ROOM) && (_purpleRoomPresenceLeo)) + || ((roomId == ROOM9) && (_room9PresenceLeo))) + bitIndex = 128; // LEO + else if (roomId == DARKBLUE_ROOM) { + if (_roomPresenceGuy) + bitIndex = 32; // GUY + if (_roomPresenceEva) + bitIndex = 16; // EVA + } else if ((roomId == BLUE_ROOM) && (_roomPresenceMax)) + bitIndex = 1; // MAX + else if ((roomId == RED_ROOM) && (_roomPresenceBob)) + bitIndex = 8; // BOB + else if ((roomId == GREEN_ROOM2) && (_roomPresencePat)) + bitIndex = 64; // PAT + else if ( ((roomId == TOILETS) && (_toiletsPresenceBobMax)) + || ((roomId == BATHROOM) && (_bathRoomPresenceBobMax)) ) + bitIndex = 9; // BOB + MAX + + if (bitIndex != 9) + showPeoplePresent(bitIndex); + + return bitIndex; +} + +/** + * Engine function - initGame + * @remarks Originally called 'dprog' + */ +void MortevielleEngine::initGame() { + _place = MANOR_FRONT; + _currentHourCount = 0; + if (!_coreVar._alreadyEnteredManor) + _blo = true; + _inGameHourDuration = kTime1; + _currentDayHour = readclock(); +} + +/** + * Engine function - Set Random Presence - Green Room + * @remarks Originally called 'pl1' + */ +void MortevielleEngine::setRandomPresenceGreenRoom(int faithScore) { + if ( ((_place == GREEN_ROOM) && (!_roomPresenceLuc) && (!_roomPresenceIda)) + || ((_place == DARKBLUE_ROOM) && (!_roomPresenceGuy) && (!_roomPresenceEva)) ) { + int p = getPresenceStatsGreenRoom(); + p += faithScore; + if (getRandomNumber(1, 100) > p) + displayAloneText(); + else + setPresenceGreenRoom(_place); + } +} + +/** + * Engine function - Set Random Presence - Purple Room + * @remarks Originally called 'pl2' + */ +void MortevielleEngine::setRandomPresencePurpleRoom(int faithScore) { + if (!_purpleRoomPresenceLeo) { + int p = getPresenceStatsPurpleRoom(); + p += faithScore; + if (getRandomNumber(1, 100) > p) + displayAloneText(); + else + setPresencePurpleRoom(); + } +} + +/** + * Engine function - Set Random Presence - Blue Room + * @remarks Originally called 'pl5' + */ +void MortevielleEngine::setRandomPresenceBlueRoom(int faithScore) { + if (!_roomPresenceMax) { + int p = getPresenceStatsBlueRoom(); + p += faithScore; + if (getRandomNumber(1, 100) > p) + displayAloneText(); + else + setPresenceBlueRoom(); + } +} + +/** + * Engine function - Set Random Presence - Red Room + * @remarks Originally called 'pl6' + */ +void MortevielleEngine::setRandomPresenceRedRoom(int faithScore) { + if ( ((_place == RED_ROOM) && (!_roomPresenceBob)) + || ((_place == GREEN_ROOM2) && (!_roomPresencePat)) ) { + int p = getPresenceStatsRedRoom(); + p += faithScore; + if (getRandomNumber(1, 100) > p) + displayAloneText(); + else + setPresenceRedRoom(_place); + } +} + +/** + * Engine function - Set Random Presence - Room 9 + * @remarks Originally called 'pl9' + */ +void MortevielleEngine::setRandomPresenceRoom9(int faithScore) { + if (!_room9PresenceLeo) { + faithScore = -10; + if (getRandomNumber(1, 100) > faithScore) // always true? + displayAloneText(); + else + setPresencePurpleRoom(); + } +} + +/** + * Engine function - Set Random Presence - Dining Room + * @remarks Originally called 'pl10' + */ +void MortevielleEngine::setRandomPresenceDiningRoom(int faithScore) { + int h; + int p = getPresenceStatsDiningRoom(h); + p += faithScore; + if (getRandomNumber(1, 100) > p) + displayAloneText(); + else + setPresenceDiningRoom(h); +} + +/** + * Engine function - Set Random Presence - Bureau + * @remarks Originally called 'pl11' + */ +void MortevielleEngine::setRandomPresenceBureau(int faithScore) { + int h; + + int p = getPresenceStatsBureau(h); + p += faithScore; + if (getRandomNumber(1, 100) > p) + displayAloneText(); + else + setPresenceBureau(h); +} + +/** + * Engine function - Set Random Presence - Kitchen + * @remarks Originally called 'pl12' + */ +void MortevielleEngine::setRandomPresenceKitchen(int faithScore) { + + int p = getPresenceStatsKitchen(); + p += faithScore; + if (getRandomNumber(1, 100) > p) + displayAloneText(); + else + setPresenceKitchen(); +} + +/** + * Engine function - Set Random Presence - Attic / Cellar + * @remarks Originally called 'pl13' + */ +void MortevielleEngine::setRandomPresenceAttic(int faithScore) { + int p = getPresenceStatsAttic(); + p += faithScore; + if (getRandomNumber(1, 100) > p) + displayAloneText(); + else + setPresenceKitchen(); +} + +/** + * Engine function - Set Random Presence - Landing + * @remarks Originally called 'pl15' + */ +void MortevielleEngine::setRandomPresenceLanding(int faithScore) { + int p = getPresenceStatsLanding(); + p += faithScore; + if (getRandomNumber(1, 100) > p) + displayAloneText(); + else + setPresenceLanding(); +} + +/** + * Engine function - Set Random Presence - Chapel + * @remarks Originally called 'pl20' + */ +void MortevielleEngine::setRandomPresenceChapel(int faithScore) { + int h; + + int p = getPresenceStatsChapel(h); + p += faithScore; + if (getRandomNumber(1, 100) > p) + displayAloneText(); + else + setPresenceChapel(h); +} + +/** + * Start music or speech + * @remarks Originally called 'musique' + */ +void MortevielleEngine::startMusicOrSpeech(int so) { + if (so == 0) { + /* musik(0) */ + ; + } else if ((!_introSpeechPlayed) && (!_coreVar._alreadyEnteredManor)) { + // Type 1: Speech + _speechManager.startSpeech(10, 1, 1); + _introSpeechPlayed = true; + } else { + if (((_coreVar._currPlace == MOUNTAIN) || (_coreVar._currPlace == MANOR_FRONT) || (_coreVar._currPlace == MANOR_BACK)) && (getRandomNumber(1, 3) == 2)) + // Type 1: Speech + _speechManager.startSpeech(9, getRandomNumber(2, 4), 1); + else if ((_coreVar._currPlace == CHAPEL) && (getRandomNumber(1, 2) == 1)) + // Type 1: Speech + _speechManager.startSpeech(8, 1, 1); + else if ((_coreVar._currPlace == WELL) && (getRandomNumber(1, 2) == 2)) + // Type 1: Speech + _speechManager.startSpeech(12, 1, 1); + else if (_coreVar._currPlace == INSIDE_WELL) + // Type 1: Speech + _speechManager.startSpeech(13, 1, 1); + else + // Type 2 : music + _speechManager.startSpeech(getRandomNumber(1, 17), 1, 2); + } +} + +/** + * Engine function - You lose! + * @remarks Originally called 'tperd' + */ +void MortevielleEngine::loseGame() { + resetOpenObjects(); + _roomDoorId = OWN_ROOM; + _mchai = 0; + _menu.unsetSearchMenu(); + if (!_blo) + getPresence(MANOR_FRONT); + + _loseGame = true; + clearUpperLeftPart(); + _screenSurface.drawBox(60, 35, 400, 50, 15); + handleDescriptionText(9, _crep); + clearDescriptionBar(); + clearVerbBar(); + _col = false; + _syn = false; + _destinationOk = false; +} + +/** + * Engine function - Check inventory for a given object + * @remarks Originally called 'cherjer' + */ +bool MortevielleEngine::checkInventory(int objectId) { + bool retVal = false; + for (int i = 1; i <= 6; ++i) + retVal = (retVal || (_coreVar._inventory[i] == objectId)); + + if (_coreVar._selectedObjectId == objectId) + retVal = true; + + return retVal; +} + +/** + * Engine function - Display Dining Room + * @remarks Originally called 'st1sama' + */ +void MortevielleEngine::displayDiningRoom() { + _coreVar._currPlace = DINING_ROOM; + prepareDisplayText(); +} + +/** + * Engine function - Start non interactive Dialog + * @remarks Originally called 'sparl' + */ +void MortevielleEngine::startDialog(int16 rep) { + const int haut[9] = { 0, 0, 1, -3, 6, -2, 2, 7, -1 }; + int key; + + assert(rep >= 0); + + _mouse.hideMouse(); + Common::String dialogStr = getString(rep + kDialogStringIndex); + _text.displayStr(dialogStr, 230, 4, 65, 24, 5); + _dialogManager.drawF3F8(); + + key = 0; + do { + _speechManager.startSpeech(rep, haut[_caff - 69], 0); + key = _dialogManager.waitForF3F8(); + if (shouldQuit()) + return; + } while (key != 66); + hirs(); + _mouse.showMouse(); +} + +/** + * Engine function - End of Search: reset globals + * @remarks Originally called 'finfouill' + */ +void MortevielleEngine::endSearch() { + _heroSearching = false; + _obpart = false; + _searchCount = 0; + _menu.unsetSearchMenu(); +} + +/** + * Engine function - Go to Dining room + * @remarks Originally called 't1sama' + */ +void MortevielleEngine::gotoDiningRoom() { + int day, hour, minute; + + updateHour(day, hour, minute); + if ((hour < 5) && (_coreVar._currPlace > ROOM18)) { + if (!checkInventory(137)) { //You don't have the keys, and it's late + _crep = 1511; + loseGame(); + } else + displayDiningRoom(); + } else if (!_coreVar._alreadyEnteredManor) { //Is it your first time? + _currBitIndex = 255; // Everybody is present + showPeoplePresent(_currBitIndex); + _caff = 77; + drawPictureWithText(); + _screenSurface.drawBox(223, 47, 155, 92, 15); + handleDescriptionText(2, 33); + testKey(false); + mennor(); + _mouse.hideMouse(); + hirs(); + premtet(); + startDialog(140); + drawRightFrame(); + drawClock(); + _mouse.showMouse(); + _coreVar._currPlace = OWN_ROOM; + prepareDisplayText(); + resetPresenceInRooms(DINING_ROOM); + if (!_blo) + getPresence(OWN_ROOM); + _currBitIndex = 0; + _savedBitIndex = 0; + _coreVar._alreadyEnteredManor = true; + } else + displayDiningRoom(); +} + +/** + * Engine function - Check Manor distance (in the mountains) + * @remarks Originally called 't1neig' + */ +void MortevielleEngine::checkManorDistance() { + ++_manorDistance; + if (_manorDistance > 2) { + _crep = 1506; + loseGame(); + } else { + _destinationOk = true; + _coreVar._currPlace = MOUNTAIN; + prepareDisplayText(); + } +} + +/** + * Engine function - Go to Manor front + * @remarks Originally called 't1deva' + */ +void MortevielleEngine::gotoManorFront() { + _manorDistance = 0; + _coreVar._currPlace = MANOR_FRONT; + prepareDisplayText(); +} + +/** + * Engine function - Go to Manor back + * @remarks Originally called 't1derr' + */ +void MortevielleEngine::gotoManorBack() { + _coreVar._currPlace = MANOR_BACK; + prepareDisplayText(); +} + +/** + * Engine function - Dead : Flooded in Well + * @remarks Originally called 't1deau' + */ +void MortevielleEngine::floodedInWell() { + _crep = 1503; + loseGame(); +} + +/** + * Engine function - Change Graphical Device + * @remarks Originally called 'change_gd' + */ +void MortevielleEngine::changeGraphicalDevice(int newDevice) { + _mouse.hideMouse(); + _currGraphicalDevice = newDevice; + hirs(); + _mouse.initMouse(); + _mouse.showMouse(); + drawRightFrame(); + prepareRoom(); + drawClock(); + if (_currBitIndex != 0) + showPeoplePresent(_currBitIndex); + else + displayAloneText(); + clearDescriptionBar(); + clearVerbBar(); + _maff = 68; + drawPictureWithText(); + handleDescriptionText(2, _crep); + _menu.displayMenu(); +} + +/** + * Called when a savegame has been loaded. + * @remarks Originally called 'antegame' + */ +void MortevielleEngine::gameLoaded() { + _mouse.hideMouse(); + _menu._menuDisplayed = false; + _loseGame = true; + _anyone = false; + _destinationOk = true; + _col = false; + _hiddenHero = false; + _uptodatePresence = false; + _maff = 68; + _menuOpcode = OPCODE_NONE; + _introSpeechPlayed = false; + _x = 0; + _y = 0; + _num = 0; + _startHour = 0; + _endHour = 0; + _searchCount = 0; + _roomDoorId = OWN_ROOM; + _syn = true; + _heroSearching = true; + _mchai = 0; + _manorDistance = 0; + resetOpenObjects(); + _takeObjCount = 0; + prepareDisplayText(); + _hintPctMessage = getString(580); + + _destinationOk = false; + _endGame = true; + _loseGame = false; + _heroSearching = false; + + displayAloneText(); + prepareRoom(); + drawClock(); + drawPictureWithText(); + handleDescriptionText(2, _crep); + clearVerbBar(); + _endGame = false; + _menu.setDestinationText(_coreVar._currPlace); + _menu.setInventoryText(); + if (_coreVar._selectedObjectId != 0) + displayItemInHand(_coreVar._selectedObjectId + 400); + _mouse.showMouse(); +} + +/** + * Engine function - Handle OpCodes + * @remarks Originally called 'tsitu' + */ +void MortevielleEngine::handleOpcode() { + if (!_col) + clearDescriptionBar(); + _syn = false; + _keyPressedEsc = false; + if (!_anyone) { + if (_uptodatePresence) { + if ((_currMenu == MENU_MOVE) || (_currAction == OPCODE_LEAVE) || (_currAction == OPCODE_SLEEP) || (_currAction == OPCODE_EAT)) { + _controlMenu = 4; + mennor(); + return; + } + } + if (_currMenu == MENU_MOVE) + fctMove(); + if (_currMenu == MENU_DISCUSS) + fctDiscuss(); + if (_currMenu == MENU_INVENTORY) + fctInventoryTake(); + if (_currAction == OPCODE_ATTACH) + fctAttach(); + if (_currAction == OPCODE_WAIT) + fctWait(); + if (_currAction == OPCODE_FORCE) + fctForce(); + if (_currAction == OPCODE_SLEEP) + fctSleep(); + if (_currAction == OPCODE_LISTEN) + fctListen(); + if (_currAction == OPCODE_ENTER) + fctEnter(); + if (_currAction == OPCODE_CLOSE) + fctClose(); + if (_currAction == OPCODE_SEARCH) + fctSearch(); + if (_currAction == OPCODE_KNOCK) + fctKnock(); + if (_currAction == OPCODE_SCRATCH) + fctScratch(); + if (_currAction == OPCODE_READ) + fctRead(); + if (_currAction == OPCODE_EAT) + fctEat(); + if (_currAction == OPCODE_PLACE) + fctPlace(); + if (_currAction == OPCODE_OPEN) + fctOpen(); + if (_currAction == OPCODE_TAKE) + fctTake(); + if (_currAction == OPCODE_LOOK) + fctLook(); + if (_currAction == OPCODE_SMELL) + fctSmell(); + if (_currAction == OPCODE_SOUND) + fctSound(); + if (_currAction == OPCODE_LEAVE) + fctLeave(); + if (_currAction == OPCODE_LIFT) + fctLift(); + if (_currAction == OPCODE_TURN) + fctTurn(); + if (_currAction == OPCODE_SSEARCH) + fctSelfSearch(); + if (_currAction == OPCODE_SREAD) + fctSelfRead(); + if (_currAction == OPCODE_SPUT) + fctSelfPut(); + if (_currAction == OPCODE_SLOOK) + fctSelftLook(); + _hiddenHero = false; + + if (_currAction == OPCODE_SHIDE) + fctSelfHide(); + } else { + if (_anyone) { + interactNPC(); + _anyone = false; + mennor(); + return; + } + } + int hour, day, minute; + updateHour(day, hour, minute); + if ((((hour == 12) || (hour == 13) || (hour == 19)) && (_coreVar._currPlace != DINING_ROOM)) || + ((hour > 0) && (hour < 6) && (_coreVar._currPlace != OWN_ROOM))) + ++_coreVar._faithScore; + if (((_coreVar._currPlace < CRYPT) || (_coreVar._currPlace > MOUNTAIN)) && (_coreVar._currPlace != INSIDE_WELL) + && (_coreVar._currPlace != OWN_ROOM) && (_coreVar._selectedObjectId != 152) && (!_loseGame)) { + if ((_coreVar._faithScore > 99) && (hour > 8) && (hour < 16)) { + _crep = 1501; + loseGame(); + } + if ((_coreVar._faithScore > 99) && (hour > 0) && (hour < 9)) { + _crep = 1508; + loseGame(); + } + if ((day > 1) && (hour > 8) && (!_loseGame)) { + _crep = 1502; + loseGame(); + } + } + mennor(); +} + +/** + * Engine function - Transform time into a char + * @remarks Originally called 'tmaj3' + */ +void MortevielleEngine::hourToChar() { + int day, hour, minute; + + updateHour(day, hour, minute); + if (minute == 30) + minute = 1; + hour += day * 24; + minute += hour * 2; + _coreVar._fullHour = (unsigned char)minute; +} + +/** + * Engine function - extract time from a char + * @remarks Originally called 'theure' + */ +void MortevielleEngine::charToHour() { + int fullHour = _coreVar._fullHour; + int tmpHour = fullHour % 48; + _currDay = fullHour / 48; + _currHalfHour = tmpHour % 2; + _currHour = tmpHour / 2; + _hour = _currHour; + if (_currHalfHour == 1) + _minute = 30; + else + _minute = 0; +} + +/** + * Engine function - Clear upper left part of Screen - Type 1 + * @remarks Originally called 'clsf1' + */ +void MortevielleEngine::clearUpperLeftPart() { + _mouse.hideMouse(); + _screenSurface.fillRect(0, Common::Rect(0, 11, 514, 175)); + _mouse.showMouse(); +} + +/** + * Engine function - Clear low bar used by description + * @remarks Originally called 'clsf2' + */ +void MortevielleEngine::clearDescriptionBar() { + _mouse.hideMouse(); + if (_largestClearScreen) { + _screenSurface.fillRect(0, Common::Rect(1, 176, 633, 199)); + _screenSurface.drawBox(0, 176, 634, 23, 15); + _largestClearScreen = false; + } else { + _screenSurface.fillRect(0, Common::Rect(1, 176, 633, 190)); + _screenSurface.drawBox(0, 176, 634, 14, 15); + } + _mouse.showMouse(); +} + +/** + * Engine function - Clear lowest bar used by verbs + * @remarks Originally called 'clsf3' + */ +void MortevielleEngine::clearVerbBar() { + _mouse.hideMouse(); + _screenSurface.fillRect(0, Common::Rect(1, 192, 633, 199)); + _screenSurface.drawBox(0, 191, 634, 8, 15); + _mouse.showMouse(); +} + +/** + * Engine function - Clear upper right part of the screen + * @remarks Originally called 'clsf10' + */ +void MortevielleEngine::clearUpperRightPart() { + int x1, x2; + Common::String st; + + _mouse.hideMouse(); + if (_resolutionScaler == 1) { + x2 = 634; + x1 = 534; + } else { + x2 = 600; + x1 = 544; + } + // Clear ambiance description + _screenSurface.fillRect(15, Common::Rect(x1, 93, x2, 98)); + if (_coreVar._faithScore < 33) + st = getEngineString(S_COOL); + else if (_coreVar._faithScore < 66) + st = getEngineString(S_LOURDE); + else if (_coreVar._faithScore > 65) + st = getEngineString(S_MALSAINE); + + x1 = 580 - (_screenSurface.getStringWidth(st) / 2); + _screenSurface.putxy(x1, 92); + _screenSurface.drawString(st, 4); + + // Clear person list + _screenSurface.fillRect(15, Common::Rect(560, 24, 610, 86)); + _mouse.showMouse(); +} + +/** + * Engine function - Get a random number between two values + * @remarks Originally called 'get_random_number' and 'hazard' + */ +int MortevielleEngine::getRandomNumber(int minval, int maxval) { + return _randomSource.getRandomNumber(maxval - minval) + minval; +} + +/** + * Engine function - Show alert "use move menu" + * @remarks Originally called 'aldepl' + */ +void MortevielleEngine::showMoveMenuAlert() { + _dialogManager.show(getEngineString(S_USE_DEP_MENU), 1); +} + +/** + * The original engine used this method to display a starting text screen letting the player + * select the graphics mode to use + * @remarks Originally called 'dialpre' + */ +void MortevielleEngine::showConfigScreen() { + _crep = 998; +} + +/** + * Decodes a number of 64 byte blocks + * @param pStart Start of data + * @param count Number of 64 byte blocks + * @remarks Originally called 'zzuul' + */ +void MortevielleEngine::decodeNumber(byte *pStart, int count) { + while (count-- > 0) { + for (int idx = 0; idx < 64; ++pStart, ++idx) { + uint16 v = ((*pStart - 0x80) << 1) + 0x80; + + if (v & 0x8000) + *pStart = 0; + else if (v & 0xff00) + *pStart = 0xff; + else + *pStart = (byte)v; + } + } +} + +const byte cryptoArrDefaultFr[32] = { + 32, 101, 115, 97, 114, 105, 110, + 117, 116, 111, 108, 13, 100, 99, + 112, 109, 46, 118, 130, 39, 102, + 98, 44, 113, 104, 103, 33, 76, + 85, 106, 30, 31 +}; + +const byte cryptoArr30Fr[32] = { + 69, 67, 74, 138, 133, 120, 77, 122, + 121, 68, 65, 63, 73, 80, 83, 82, + 156, 45, 58, 79, 49, 86, 78, 84, + 71, 81, 64, 66, 135, 34, 136, 91 +}; + +const byte cryptoArr31Fr[32]= { + 93, 47, 48, 53, 50, 70, 124, 75, + 72, 147, 140, 150, 151, 57, 56, 51, + 107, 139, 55, 89, 131, 37, 54, 88, + 119, 0, 0, 0, 0, 0, 0, 0 +}; + +const byte cryptoArrDefaultDe[32] = { + 0x20, 0x65, 0x6E, 0x69, 0x73, 0x72, 0x74, + 0x68, 0x61, 0x75, 0x0D, 0x63, 0x6C, 0x64, + 0x6D, 0x6F, 0x67, 0x2E, 0x62, 0x66, 0x53, + 0x2C, 0x77, 0x45, 0x7A, 0x6B, 0x44, 0x76, + 0x9C, 0x47, 0x1E, 0x1F +}; + +const byte cryptoArr30De[32] = { + 0x49, 0x4D, 0x21, 0x42, 0x4C, 0x70, 0x41, 0x52, + 0x57, 0x4E, 0x48, 0x3F, 0x46, 0x50, 0x55, 0x4B, + 0x5A, 0x4A, 0x54, 0x31, 0x4F, 0x56, 0x79, 0x3A, + 0x6A, 0x5B, 0x5D, 0x40, 0x22, 0x2F, 0x30, 0x35 +}; + +const byte cryptoArr31De[32]= { + 0x78, 0x2D, 0x32, 0x82, 0x43, 0x39, 0x33, 0x38, + 0x7C, 0x27, 0x37, 0x3B, 0x25, 0x28, 0x29, 0x36, + 0x51, 0x59, 0x71, 0x81, 0x87, 0x88, 0x93, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +const byte *cryptoArrDefault, *cryptoArr30, *cryptoArr31; +uint16 ctrlChar; + +/** + * Decrypt the next character + * @param c OUT, next decrypted char + * @param idx IN/OUT, current buffer index + * @param pt IN/OUT, current encryption point + * @return a boolean specifying if a stop character has been encountered + * @remarks Originally called 'cinq_huit' + */ +bool MortevielleEngine::decryptNextChar(char &c, int &idx, byte &pt) { + uint16 oct, ocd; + + /* 5-8 */ + oct = _dialogIndexArray[idx]; + oct = ((uint16)(oct << (16 - pt))) >> (16 - pt); + if (pt < 6) { + ++idx; + oct = oct << (5 - pt); + pt += 11; + oct = oct | ((uint)_dialogIndexArray[idx] >> pt); + } else { + pt -= 5; + oct = (uint)oct >> pt; + } + + if (oct == ctrlChar) { + c = '$'; + return true; + } else if (oct == 30 || oct == 31) { + ocd = _dialogIndexArray[idx]; + ocd = (uint16)(ocd << (16 - pt)) >> (16 - pt); + if (pt < 6) { + ++idx; + ocd = ocd << (5 - pt); + pt += 11; + ocd = ocd | ((uint)_dialogIndexArray[idx] >> pt); + } else { + pt -= 5; + ocd = (uint)ocd >> pt; + } + + if (oct == 30) + c = (unsigned char)cryptoArr30[ocd]; + else + c = (unsigned char)cryptoArr31[ocd]; + + if (c == '\0') { + c = '#'; + return true; + } + } else { + c = (unsigned char)cryptoArrDefault[oct]; + } + return false; +} + +/** + * Decode and extract the line with the given Id + * @remarks Originally called 'deline' + */ +Common::String MortevielleEngine::getString(int num) { + Common::String wrkStr = ""; + + if (num < 0) { + warning("getString(%d): num < 0! Skipping", num); + } else if (!_txxFileFl) { + wrkStr = getGameString(num); + } else { + int hint = _dialogHintArray[num]._hintId; + byte point = _dialogHintArray[num]._point; + int length = 0; + bool endFl = false; + char let; + do { + endFl = decryptNextChar(let, hint, point); + wrkStr += let; + ++length; + } while (!endFl); + } + + while (wrkStr.lastChar() == '$') + // Remove trailing '$'s + wrkStr.deleteLastChar(); + + return wrkStr; +} + +void MortevielleEngine::copcha() { + for (int i = kAcha; i < kAcha + 390; i++) + _tabdon[i] = _tabdon[i + 390]; +} + +/** + * Engine function - When restarting the game, reset the main variables used by the engine + * @remarks Originally called 'inzon' + */ +void MortevielleEngine::resetVariables() { + copcha(); + + _coreVar._alreadyEnteredManor = false; + _coreVar._selectedObjectId = 0; + _coreVar._cellarObjectId = 0; + _coreVar._atticBallHoleObjectId = 0; + _coreVar._atticRodHoleObjectId = 0; + _coreVar._wellObjectId = 0; + _coreVar._secretPassageObjectId = 0; + _coreVar._purpleRoomObjectId = 136; + _coreVar._cryptObjectId = 141; + _coreVar._faithScore = getRandomNumber(4, 10); + _coreVar._currPlace = MANOR_FRONT; + + for (int i = 2; i <= 6; ++i) + _coreVar._inventory[i] = 0; + + // Only object in inventory: a gun + _coreVar._inventory[1] = 113; + + _coreVar._fullHour = (unsigned char)20; + + for (int i = 1; i <= 10; ++i) + _coreVar._pctHintFound[i] = ' '; + + for (int i = 1; i <= 6; ++i) + _coreVar._availableQuestion[i] = '*'; + + for (int i = 7; i <= 9; ++i) + _coreVar._availableQuestion[i] = ' '; + + for (int i = 10; i <= 28; ++i) + _coreVar._availableQuestion[i] = '*'; + + for (int i = 29; i <= 42; ++i) + _coreVar._availableQuestion[i] = ' '; + + _coreVar._availableQuestion[33] = '*'; + + for (int i = 1; i <= 8; ++i) + _charAnswerCount[i] = 0; + + initMaxAnswer(); +} + +/** + * Engine function - Set the palette + * @remarks Originally called 'writepal' + */ +void MortevielleEngine::setPal(int n) { + switch (_currGraphicalDevice) { + case MODE_TANDY: + case MODE_EGA: + case MODE_AMSTRAD1512: + for (int i = 1; i <= 16; ++i) { + _curPict[(2 * i)] = _stdPal[n][i].x; + _curPict[(2 * i) + 1] = _stdPal[n][i].y; + } + break; + case MODE_CGA: { + nhom pal[16]; + for (int i = 0; i < 16; ++i) { + pal[i] = _cgaPal[n]._a[i]; + } + + if (n < 89) + palette(_cgaPal[n]._p); + + for (int i = 0; i <= 15; ++i) + displayCGAPattern(i, _patternArr[pal[i]._id], pal); + } + break; + default: + break; + } +} + +/** + * Engine function - Display a CGA pattern, using a specified palette + * @remarks Originally called 'outbloc' + */ +void MortevielleEngine::displayCGAPattern(int n, Pattern p, nhom *pal) { + int addr = n * 404 + 0xd700; + + WRITE_LE_UINT16(&_curPict[addr], p._tax); + WRITE_LE_UINT16(&_curPict[addr + 2], p._tay); + addr += 4; + for (int i = 0; i < p._tax; ++i) { + for (int j = 0; j < p._tay; ++j) + _curPict[addr + j * p._tax + i] = pal[n]._hom[p._des[i + 1][j + 1]]; + } +} + +/** + * Engine function - Load Palette from File + * @remarks Originally called 'charpal' + */ +void MortevielleEngine::loadPalette() { + Common::File f; + byte b; + + if (!f.open("fxx.mor")) { + if (f.open("mfxx.mor")) + f.seek(7 * 25); + else + error("Missing file - fxx.mor"); + } + + for (int i = 0; i < 108; ++i) + _drawingSizeArr[i] = f.readSint16LE(); + f.close(); + + if (!f.open("plxx.mor")) + error("Missing file - plxx.mor"); + for (int i = 0; i <= 90; ++i) { + for (int j = 1; j <= 16; ++j) { + _stdPal[i][j].x = f.readByte(); + _stdPal[i][j].y = f.readByte(); + } + } + f.close(); + + if (!f.open("cxx.mor")) + error("Missing file - cxx.mor"); + + for (int j = 0; j <= 90; ++j) { + _cgaPal[j]._p = f.readByte(); + for (int i = 0; i <= 15; ++i) { + nhom &with = _cgaPal[j]._a[i]; + + b = f.readByte(); + with._id = (uint)b >> 4; + with._hom[0] = ((uint)b >> 2) & 3; + with._hom[1] = b & 3; + } + } + + _cgaPal[10]._a[9] = _cgaPal[10]._a[5]; + for (int j = 0; j <= 14; ++j) { + _patternArr[j]._tax = f.readByte(); + _patternArr[j]._tay = f.readByte(); + for (int i = 1; i <= 20; ++i) { + for (int k = 1; k <= 20; ++k) + _patternArr[j]._des[i][k] = f.readByte(); + } + } + f.close(); +} + +/** + * Engine function - Load Texts from File + * @remarks Originally called 'chartex' + */ +void MortevielleEngine::loadTexts() { + Common::File inpFile; + Common::File ntpFile; + + _txxFileFl = false; + if (getLanguage() == Common::EN_ANY) { + warning("English version expected - Switching to DAT file"); + return; + } + + if (!inpFile.open("TXX.INP")) { + if (!inpFile.open("TXX.MOR")) { + warning("Missing file - TXX.INP or .MOR - Switching to DAT file"); + return; + } + } + if (ntpFile.open("TXX.NTP")) { + cryptoArr30 = cryptoArr30Fr; + cryptoArr31 = cryptoArr31Fr; + cryptoArrDefault = cryptoArrDefaultFr; + ctrlChar = 11; + } else if (ntpFile.open("TXX.IND")) { + cryptoArr30 = cryptoArr30De; + cryptoArr31 = cryptoArr31De; + cryptoArrDefault = cryptoArrDefaultDe; + ctrlChar = 10; + } else { + warning("Missing file - TXX.NTP or .IND - Switching to DAT file"); + return; + } + + if ((inpFile.size() > (kMaxDialogIndex * 2)) || (ntpFile.size() > (kMaxDialogHint * 3))) { + warning("TXX file - Unexpected format - Switching to DAT file"); + return; + } + + for (int i = 0; i < inpFile.size() / 2; ++i) + _dialogIndexArray[i] = inpFile.readUint16LE(); + + inpFile.close(); + _txxFileFl = true; + + for (int i = 0; i < (ntpFile.size() / 3); ++i) { + _dialogHintArray[i]._hintId = ntpFile.readSint16LE(); + _dialogHintArray[i]._point = ntpFile.readByte(); + } + + ntpFile.close(); + +} + +void MortevielleEngine::loadBRUIT5() { + Common::File f; + + if (!f.open("bruit5")) + error("Missing file - bruit5"); + + free(_speechManager._noise5Buf); + _speechManager._noise5Size = f.size(); + _speechManager._noise5Buf = (byte *)malloc(sizeof(byte) * _speechManager._noise5Size); + f.read(_speechManager._noise5Buf, _speechManager._noise5Size); + f.close(); +} + +void MortevielleEngine::loadCFIEC() { + Common::File f; + + if (!f.open("cfiec.mor")) { + if (!f.open("alcfiec.mor")) + error("Missing file - *cfiec.mor"); + } + + _cfiecBufferSize = ((f.size() / 128) + 1) * 128; + int32 fileSize = f.size(); + + if (!_reloadCFIEC) + _cfiecBuffer = (byte *)malloc(sizeof(byte) * _cfiecBufferSize); + + for (int32 i = 0; i < fileSize; ++i) + _cfiecBuffer[i] = f.readByte(); + + for (int i = fileSize; i < _cfiecBufferSize; i++) + _cfiecBuffer[i] = 0; + + f.close(); + + _reloadCFIEC = false; +} + + +void MortevielleEngine::loadCFIPH() { + Common::File f; + + if (!f.open("cfiph.mor")) { + if (!f.open("alcfiph.mor")) + error("Missing file - *cfiph.mor"); + } + + _speechManager._cfiphBuffer = (uint16 *)malloc(sizeof(uint16) * (f.size() / 2)); + + for (int i = 0; i < (f.size() / 2); ++i) + _speechManager._cfiphBuffer[i] = f.readUint16BE(); + + f.close(); +} + +/** + * Engine function - Play Music + * @remarks Originally called 'music' + */ +void MortevielleEngine::music() { + if (_soundOff) + return; + + _reloadCFIEC = true; + + Common::File f; + if (!f.open("mort.img")) + error("Missing file - mort.img"); + + free(_compMusicBuf2); + int size = f.size(); + _compMusicBuf2 = (byte *)malloc(sizeof(byte) * size); + f.read(_compMusicBuf2, size); + f.close(); + + _soundManager.decodeMusic(_compMusicBuf2, &_mem[kAdrMusic * 16], size / 128); + _addFix = (float)((kTempoMusic - 8)) / 256; + _speechManager.cctable(_speechManager._tbi); + + bool fin = false; + int k = 0; + do { + fin = keyPressed(); + _soundManager.musyc(_speechManager._tbi, 9958, kTempoMusic); + ++k; + fin = fin | keyPressed() | (k >= 5); + } while (!fin); + while (keyPressed()) + getChar(); +} + +/** + * Engine function - Show title screen + * @remarks Originally called 'suite' + */ +void MortevielleEngine::showTitleScreen() { + hirs(); + handleDescriptionText(7, 2035); + _caff = 51; + _text.taffich(); + testKeyboard(); + if (_newGraphicalDevice != _currGraphicalDevice) + _currGraphicalDevice = _newGraphicalDevice; + hirs(); + draw(0, 0); + + Common::String cpr = "COPYRIGHT 1989 : LANKHOR"; + _screenSurface.putxy(104 + 72 * _resolutionScaler, 185); + _screenSurface.drawString(cpr, 0); +} + +/** + * Draw picture + * @remarks Originally called 'dessine' + */ +void MortevielleEngine::draw(int x, int y) { + _mouse.hideMouse(); + setPal(_numpal); + displayPicture(_curPict, x, y); + _mouse.showMouse(); +} + +/** + * Draw right frame + * @remarks Originally called 'dessine_rouleau' + */ +void MortevielleEngine::drawRightFrame() { + setPal(89); + if (_currGraphicalDevice == MODE_HERCULES) + _curPict[14] = 15; + + _mouse.hideMouse(); + displayPicture(_rightFramePict, 0, 0); + _mouse.showMouse(); +} + +/** + * Read the current system time + */ +int MortevielleEngine::readclock() { + TimeDate dateTime; + g_system->getTimeAndDate(dateTime); + + int m = dateTime.tm_min * 60; + int h = dateTime.tm_hour * 3600; + return h + m + dateTime.tm_sec; +} + +/** + * Engine function - Prepare room and hint string + * @remarks Originally called 'tinke' + */ +void MortevielleEngine::prepareRoom() { + int day, hour, minute; + + _anyone = false; + updateHour(day, hour, minute); + if (day != _day) { + _day = day; + for (int i = 0; i < 9; i++) { + if (_charAnswerMax[i] > 0) + --_charAnswerMax[i]; + _charAnswerCount[i] = 0; + } + } + if ((hour > _hour) || ((hour == 0) && (_hour == 23))) { + _hour = hour; + _minute = 0; + drawClock(); + int hintCount = 0; + for (int i = 1; i <= 10; ++i) { + if (_coreVar._pctHintFound[i] == '*') + ++hintCount; + } + + Common::String pctStr; + if (hintCount == 10) + pctStr = "10"; + else + pctStr = (unsigned char)(hintCount + 48); + + _hintPctMessage = "[1]["; + _hintPctMessage += getEngineString(S_SHOULD_HAVE_NOTICED); + _hintPctMessage += pctStr; + _hintPctMessage += '0'; + _hintPctMessage += getEngineString(S_NUMBER_OF_HINTS); + _hintPctMessage += "]["; + _hintPctMessage += getEngineString(S_OK); + _hintPctMessage += ']'; + } + if (minute > _minute) { + _minute = 30; + drawClock(); + } + if (_mouse._pos.y < 12) + return; + + if (!_blo) { + if ((hour == 12) || ((hour > 18) && (hour < 21)) || ((hour >= 0) && (hour < 7))) + _inGameHourDuration = kTime2; + else + _inGameHourDuration = kTime1; + if ((_coreVar._faithScore > 33) && (_coreVar._faithScore < 66)) + _inGameHourDuration -= (_inGameHourDuration / 3); + + if (_coreVar._faithScore > 65) + _inGameHourDuration -= ((_inGameHourDuration / 3) * 2); + + int newHour = readclock(); + if ((newHour - _currentDayHour) > _inGameHourDuration) { + bool activeMenu = _menu._menuActive; + _menu.eraseMenu(); + _currentHourCount += ((newHour - _currentDayHour) / _inGameHourDuration); + _currentDayHour = newHour; + switch (_place) { + case GREEN_ROOM: + case DARKBLUE_ROOM: + setRandomPresenceGreenRoom(_coreVar._faithScore); + break; + case PURPLE_ROOM: + setRandomPresencePurpleRoom(_coreVar._faithScore); + break; + case BLUE_ROOM: + setRandomPresenceBlueRoom(_coreVar._faithScore); + break; + case RED_ROOM: + case GREEN_ROOM2: + setRandomPresenceRedRoom(_coreVar._faithScore); + break; + case ROOM9: + setRandomPresenceRoom9(_coreVar._faithScore); + break; + case DINING_ROOM: + setRandomPresenceDiningRoom(_coreVar._faithScore); + break; + case BUREAU: + setRandomPresenceBureau(_coreVar._faithScore); + break; + case KITCHEN: + setRandomPresenceKitchen(_coreVar._faithScore); + break; + case ATTIC: + case CELLAR: + setRandomPresenceAttic(_coreVar._faithScore); + break; + case LANDING: + case ROOM26: + setRandomPresenceLanding(_coreVar._faithScore); + break; + case CHAPEL: + setRandomPresenceChapel(_coreVar._faithScore); + break; + } + if ((_savedBitIndex != 0) && (_currBitIndex != 10)) + _savedBitIndex = _currBitIndex; + + if ((_savedBitIndex == 0) && (_currBitIndex > 0)) { + if ((_coreVar._currPlace == ATTIC) || (_coreVar._currPlace == CELLAR)) { + initCaveOrCellar(); + } else if (_currBitIndex == 10) { + _currBitIndex = 0; + if (!_uptodatePresence) { + _uptodatePresence = true; + _startHour = readclock(); + if (getRandomNumber(1, 5) < 5) { + clearVerbBar(); + prepareScreenType2(); + displayTextInVerbBar(getEngineString(S_HEAR_NOISE)); + int rand = (getRandomNumber(0, 4)) - 2; + _speechManager.startSpeech(1, rand, 1); + clearVerbBar(); + } + } + } + } + + if (activeMenu) + _menu.drawMenu(); + } + } + _endHour = readclock(); + if ((_uptodatePresence) && ((_endHour - _startHour) > 17)) { + getPresenceBitIndex(_place); + _uptodatePresence = false; + _startHour = 0; + if ((_coreVar._currPlace > OWN_ROOM) && (_coreVar._currPlace < DINING_ROOM)) + _anyone = true; + } +} + +/** + * Engine function - Draw Clock + * @remarks Originally called 'pendule' + */ +void MortevielleEngine::drawClock() { + const int cv[2][12] = { + { 5, 8, 10, 8, 5, 0, -5, -8, -10, -8, -5, 0 }, + { -5, -3, 0, 3, 5, 6, 5, 3, 0, -3, -5, -6 } + }; + const int x = 580; + const int y = 123; + const int rg = 9; + int hourColor; + + _mouse.hideMouse(); + + _screenSurface.drawRectangle(570, 118, 20, 10); + _screenSurface.drawRectangle(578, 114, 6, 18); + if ((_currGraphicalDevice == MODE_CGA) || (_currGraphicalDevice == MODE_HERCULES)) + hourColor = 0; + else + hourColor = 1; + + if (_minute == 0) + _screenSurface.drawLine(((uint)x >> 1) * _resolutionScaler, y, ((uint)x >> 1) * _resolutionScaler, (y - rg), hourColor); + else + _screenSurface.drawLine(((uint)x >> 1) * _resolutionScaler, y, ((uint)x >> 1) * _resolutionScaler, (y + rg), hourColor); + + int hour12 = _hour; + if (hour12 > 12) + hour12 -= 12; + if (hour12 == 0) + hour12 = 12; + + _screenSurface.drawLine(((uint)x >> 1) * _resolutionScaler, y, ((uint)(x + cv[0][hour12 - 1]) >> 1) * _resolutionScaler, y + cv[1][hour12 - 1], hourColor); + _mouse.showMouse(); + _screenSurface.putxy(568, 154); + + if (_hour > 11) + _screenSurface.drawString("PM ", 1); + else + _screenSurface.drawString("AM ", 1); + + _screenSurface.putxy(550, 160); + if ((_day >= 0) && (_day <= 8)) { + Common::String tmp = getEngineString(S_DAY); + tmp.insertChar((char)(_day + 49), 0); + _screenSurface.drawString(tmp, 1); + } +} + +void MortevielleEngine::palette(int v1) { + warning("TODO: palette"); +} + +/** + * Returns a substring of the given string + * @param s Source string + * @param idx Starting index (1 based) + * @param size Number of characters to return + */ + +Common::String MortevielleEngine::copy(const Common::String &s, int idx, size_t size) { + assert(idx + size < s.size()); + + // Copy the substring into a temporary buffer + char *tmp = new char[size + 1]; + strncpy(tmp, s.c_str() + idx - 1, size); + tmp[size] = '\0'; + + Common::String result(tmp); + delete[] tmp; + return result; +} + +void MortevielleEngine::hirs() { + // Note: The original used this to set the graphics mode and clear the screen, both at + // the start of the game, and whenever the screen need to be cleared. As such, this + // method is deprecated in favour of clearing the screen + debugC(1, kMortevielleCore, "TODO: hirs is deprecated in favour of ScreenSurface::clearScreen"); + + if (_currGraphicalDevice == MODE_TANDY) { + _screenSurface.fillRect(0, Common::Rect(0, 0, 639, 200)); + _resolutionScaler = 1; + } else if (_currGraphicalDevice == MODE_CGA) { + palette(1); + _resolutionScaler = 1; + } else + _resolutionScaler = 2; + + _screenSurface.clearScreen(); +} + +/** + * Init room : Cave or Cellar + * @remarks Originally called 'cavegre' + */ +void MortevielleEngine::initCaveOrCellar() { + _coreVar._faithScore += 2; + if (_coreVar._faithScore > 69) + _coreVar._faithScore += (_coreVar._faithScore / 10); + clearVerbBar(); + prepareScreenType2(); + displayTextInVerbBar(getEngineString(S_SOMEONE_ENTERS)); + int rand = (getRandomNumber(0, 4)) - 2; + _speechManager.startSpeech(2, rand, 1); + + // The original was doing here a useless loop. + // It has been removed + + clearVerbBar(); + displayAloneText(); +} + +/** + * Display control menu string + * @remarks Originally called 'tctrm' + */ +void MortevielleEngine::displayControlMenu() { + handleDescriptionText(2, (3000 + _controlMenu)); + _controlMenu = 0; +} + +/** + * Display picture at a given coordinate + * @remarks Originally called 'pictout' + */ +void MortevielleEngine::displayPicture(const byte *pic, int x, int y) { + GfxSurface surface; + surface.decode(pic); + + if (_currGraphicalDevice == MODE_HERCULES) { + _curPict[2] = 0; + _curPict[32] = 15; + } + + _screenSurface.drawPicture(surface, x, y); +} + +void MortevielleEngine::adzon() { + Common::File f; + + if (!f.open("don.mor")) + error("Missing file - don.mor"); + + f.read(_tabdon, 7 * 256); + f.close(); + + if (!f.open("bmor.mor")) + error("Missing file - bmor.mor"); + + f.read(&_tabdon[kFleche], 1916); + f.close(); + + // Read Right Frame Drawing + if (!f.open("dec.mor")) + error("Missing file - dec.mor"); + + free(_rightFramePict); + _rightFramePict = (byte *)malloc(sizeof(byte) * f.size()); + f.read(_rightFramePict, f.size()); + f.close(); +} + +/** + * Returns the offset within the compressed image data resource of the desired image + * @remarks Originally called 'animof' + */ +int MortevielleEngine::getAnimOffset(int frameNum, int animNum) { + int animCount = _curAnim[1]; + int aux = animNum; + if (frameNum != 1) + aux += animCount; + + return (animCount << 2) + 2 + READ_BE_UINT16(&_curAnim[aux << 1]); +} + +/** + * Display text in description bar + * @remarks Originally called 'text1' + */ +void MortevielleEngine::displayTextInDescriptionBar(int x, int y, int nb, int mesgId) { + int co; + + if (_resolutionScaler == 1) + co = 10; + else + co = 6; + Common::String tmpStr = getString(mesgId); + if ((y == 182) && ((int) tmpStr.size() * co > nb * 6)) + y = 176; + _text.displayStr(tmpStr, x, y, nb, 20, _textColor); +} + +/** + * Display description text + * @remarks Originally called 'repon' + */ +void MortevielleEngine::handleDescriptionText(int f, int mesgId) { + if ((mesgId > 499) && (mesgId < 563)) { + Common::String tmpStr = getString(mesgId - 501 + kInventoryStringIndex); + + if ((int) tmpStr.size() > ((58 + (_resolutionScaler - 1) * 37) << 1)) + _largestClearScreen = true; + else + _largestClearScreen = false; + + clearDescriptionBar(); + _text.displayStr(tmpStr, 8, 176, 85, 3, 5); + } else { + mapMessageId(mesgId); + switch (f) { + case 2: + case 8: + clearDescriptionBar(); + prepareScreenType2(); + displayTextInDescriptionBar(8, 182, 103, mesgId); + if ((mesgId == 68) || (mesgId == 69)) + _coreVar._availableQuestion[40] = '*'; + if ((mesgId == 104) && (_caff == CELLAR)) { + _coreVar._availableQuestion[36] = '*'; + if (_coreVar._availableQuestion[39] == '*') { + _coreVar._pctHintFound[3] = '*'; + _coreVar._availableQuestion[38] = '*'; + } + } + break; + case 1: + case 6: + case 9: { + int i; + if ((f == 1) || (f == 6)) + i = 4; + else + i = 5; + + Common::String tmpStr = getString(mesgId); + _text.displayStr(tmpStr, 80, 40, 60, 25, i); + + if (mesgId == 180) + _coreVar._pctHintFound[6] = '*'; + else if (mesgId == 179) + _coreVar._pctHintFound[10] = '*'; + } + break; + default: + break; + } + } +} + +/** + * Recompute message Id + * @remarks Originally called 'modif' + */ +void MortevielleEngine::mapMessageId(int &mesgId) { + if (mesgId == 26) + mesgId = 25; + else if ((mesgId > 29) && (mesgId < 36)) + mesgId -= 4; + else if ((mesgId > 69) && (mesgId < 78)) + mesgId -= 37; + else if ((mesgId > 99) && (mesgId < 194)) + mesgId -= 59; + else if ((mesgId > 996) && (mesgId < 1000)) + mesgId -= 862; + else if ((mesgId > 1500) && (mesgId < 1507)) + mesgId -= 1363; + else if ((mesgId > 1507) && (mesgId < 1513)) + mesgId -= 1364; + else if ((mesgId > 1999) && (mesgId < 2002)) + mesgId -= 1851; + else if (mesgId == 2010) + mesgId = 151; + else if ((mesgId > 2011) && (mesgId < 2025)) + mesgId -= 1860; + else if (mesgId == 2026) + mesgId = 165; + else if ((mesgId > 2029) && (mesgId < 2037)) + mesgId -= 1864; + else if ((mesgId > 3000) && (mesgId < 3005)) + mesgId -= 2828; + else if (mesgId == 4100) + mesgId = 177; + else if (mesgId == 4150) + mesgId = 178; + else if ((mesgId > 4151) && (mesgId < 4156)) + mesgId -= 3973; + else if (mesgId == 4157) + mesgId = 183; + else if ((mesgId == 4160) || (mesgId == 4161)) + mesgId -= 3976; +} + +/** + * Initialize open objects array + * @remarks Originally called 'initouv' + */ +void MortevielleEngine::resetOpenObjects() { + for (int cx = 1; cx <= 7; ++cx) + _openObjects[cx] = 0; + _openObjCount = 0; +} + +void MortevielleEngine::ecr2(Common::String text) { + // Some dead code was present in the original: removed + _screenSurface.putxy(8, 177); + int tlig = 59 + (_resolutionScaler - 1) * 36; + + if ((int)text.size() < tlig) + _screenSurface.drawString(text, 5); + else if ((int)text.size() < (tlig << 1)) { + _screenSurface.putxy(8, 176); + _screenSurface.drawString(copy(text, 1, (tlig - 1)), 5); + _screenSurface.putxy(8, 182); + _screenSurface.drawString(copy(text, tlig, tlig << 1), 5); + } else { + _largestClearScreen = true; + clearDescriptionBar(); + _screenSurface.putxy(8, 176); + _screenSurface.drawString(copy(text, 1, (tlig - 1)), 5); + _screenSurface.putxy(8, 182); + _screenSurface.drawString(copy(text, tlig, ((tlig << 1) - 1)), 5); + _screenSurface.putxy(8, 190); + _screenSurface.drawString(copy(text, tlig << 1, tlig * 3), 5); + } +} + +void MortevielleEngine::displayTextInVerbBar(Common::String text) { + clearVerbBar(); + _screenSurface.putxy(8, 192); + _screenSurface.drawString(text, 5); +} + +/** + * Display item in hand + * @remarks Originally called 'modobj' + */ +void MortevielleEngine::displayItemInHand(int objId) { + Common::String strp = Common::String(' '); + + if (objId != 500) + strp = getString(objId - 501 + kInventoryStringIndex); + + _menu.setText(_menu._inventoryMenu[8]._menuId, _menu._inventoryMenu[8]._actionId, strp); + _menu.disableMenuItem(_menu._inventoryMenu[8]._menuId, _menu._inventoryMenu[8]._actionId); +} + +/** + * Display empty hand + * @remarks Originally called 'maivid' + */ +void MortevielleEngine::displayEmptyHand() { + _coreVar._selectedObjectId = 0; + displayItemInHand(500); +} + +/** + * Set a random presence: Leo or Max + * @remarks Originally called 'chlm' + */ +int MortevielleEngine::checkLeoMaxRandomPresence() { + int retval = getRandomNumber(1, 2); + if (retval == 2) + retval = 128; + + return retval; +} + +/** + * Reset room variables + * @remarks Originally called 'debloc' + */ +void MortevielleEngine::resetRoomVariables(int roomId) { + _num = 0; + _x = 0; + _y = 0; + if ((roomId != ROOM26) && (roomId != LANDING)) + resetPresenceInRooms(roomId); + _savedBitIndex = _currBitIndex; +} + +/** + * Compute presence stats + * @remarks Originally called 'ecfren' + */ +int MortevielleEngine::getPresenceStats(int &rand, int faithScore, int roomId) { + if (roomId == OWN_ROOM) + displayAloneText(); + int retVal = -500; + rand = 0; + if ( ((roomId == GREEN_ROOM) && (!_roomPresenceLuc) && (!_roomPresenceIda)) + || ((roomId == DARKBLUE_ROOM) && (!_roomPresenceGuy) && (!_roomPresenceEva)) ) + retVal = getPresenceStatsGreenRoom(); + if ((roomId == PURPLE_ROOM) && (!_purpleRoomPresenceLeo) && (!_room9PresenceLeo)) + retVal = getPresenceStatsPurpleRoom(); + if ( ((roomId == TOILETS) && (!_toiletsPresenceBobMax)) + || ((roomId == BATHROOM) && (!_bathRoomPresenceBobMax)) ) + retVal = getPresenceStatsToilets(); + if ((roomId == BLUE_ROOM) && (!_roomPresenceMax)) + retVal = getPresenceStatsBlueRoom(); + if ( ((roomId == RED_ROOM) && (!_roomPresenceBob)) + || ((roomId == GREEN_ROOM2) && (!_roomPresencePat))) + retVal = getPresenceStatsRedRoom(); + if ((roomId == ROOM9) && (!_room9PresenceLeo) && (!_purpleRoomPresenceLeo)) + retVal = 10; + if ( ((roomId == PURPLE_ROOM) && (_room9PresenceLeo)) + || ((roomId == ROOM9) && (_purpleRoomPresenceLeo))) + retVal = -400; + if (retVal != -500) { + retVal += faithScore; + rand = getRandomNumber(1, 100); + } + + return retVal; +} + +/** + * Set presence flags + * @remarks Originally called 'becfren' + */ +void MortevielleEngine::setPresenceFlags(int roomId) { + if ((roomId == GREEN_ROOM) || (roomId == DARKBLUE_ROOM)) { + int rand = getRandomNumber(1, 2); + if (roomId == GREEN_ROOM) { + if (rand == 1) + _roomPresenceLuc = true; + else + _roomPresenceIda = true; + } else { // roomId == DARKBLUE_ROOM + if (rand == 1) + _roomPresenceGuy = true; + else + _roomPresenceEva = true; + } + } else if (roomId == PURPLE_ROOM) + _purpleRoomPresenceLeo = true; + else if (roomId == TOILETS) + _toiletsPresenceBobMax = true; + else if (roomId == BLUE_ROOM) + _roomPresenceMax = true; + else if (roomId == RED_ROOM) + _roomPresenceBob = true; + else if (roomId == BATHROOM) + _bathRoomPresenceBobMax = true; + else if (roomId == GREEN_ROOM2) + _roomPresencePat = true; + else if (roomId == ROOM9) + _room9PresenceLeo = true; +} + +/** + * Initialize max answers per character + * @remarks Originally called 'init_nbrepm' + */ +void MortevielleEngine::initMaxAnswer() { + static const byte maxAnswer[9] = { 0, 4, 5, 6, 7, 5, 6, 5, 8 }; + + for (int idx = 0; idx < 9; ++idx) + _charAnswerMax[idx] = maxAnswer[idx]; +} + +/** + * Get Presence + * @remarks Originally called 't11' + */ +int MortevielleEngine::getPresence(int roomId) { + int retVal = 0; + int rand; + + int p = getPresenceStats(rand, _coreVar._faithScore, roomId); + _place = roomId; + if ((roomId > OWN_ROOM) && (roomId < DINING_ROOM)) { + if (p != -500) { + if (rand > p) { + displayAloneText(); + retVal = 0; + } else { + setPresenceFlags(_place); + retVal = getPresenceBitIndex(_place); + } + } else + retVal = getPresenceBitIndex(_place); + } + + if (roomId > ROOM9) { + if ((roomId > LANDING) && (roomId != CHAPEL) && (roomId != ROOM26)) + displayAloneText(); + else { + int h = 0; + if (roomId == DINING_ROOM) + p = getPresenceStatsDiningRoom(h); + else if (roomId == BUREAU) + p = getPresenceStatsBureau(h); + else if (roomId == KITCHEN) + p = getPresenceStatsKitchen(); + else if ((roomId == ATTIC) || (roomId == CELLAR)) + p = getPresenceStatsAttic(); + else if ((roomId == LANDING) || (roomId == ROOM26)) + p = getPresenceStatsLanding(); + else if (roomId == CHAPEL) + p = getPresenceStatsChapel(h); + p += _coreVar._faithScore; + rand = getRandomNumber(1, 100); + if (rand > p) { + displayAloneText(); + retVal = 0; + } else { + if (roomId == DINING_ROOM) + p = setPresenceDiningRoom(h); + else if (roomId == BUREAU) + p = setPresenceBureau(h); + else if ((roomId == KITCHEN) || (roomId == ATTIC) || (roomId == CELLAR)) + p = setPresenceKitchen(); + else if ((roomId == LANDING) || (roomId == ROOM26)) + p = setPresenceLanding(); + else if (roomId == CHAPEL) + p = setPresenceChapel(h); + retVal = p; + } + } + } + + return retVal; +} + +/** + * Display Question String + * @remarks Originally called 'writetp' + */ +void MortevielleEngine::displayQuestionText(Common::String s, int cmd) { + if (_resolutionScaler == 2) + _screenSurface.drawString(s, cmd); + else + _screenSurface.drawString(copy(s, 1, 25), cmd); +} + +/** + * Display animation frame + * @remarks Originally called 'aniof' + */ +void MortevielleEngine::displayAnimFrame(int frameNum, int animId) { + if ((_caff == BATHROOM) && ((animId == 4) || (animId == 5))) + return; + + if ((_caff == DINING_ROOM) && (animId == 7)) + animId = 6; + else if (_caff == KITCHEN) { + if (animId == 3) + animId = 4; + else if (animId == 4) + animId = 3; + } + + int offset = getAnimOffset(frameNum, animId); + + GfxSurface surface; + surface.decode(&_curAnim[offset]); + _screenSurface.drawPicture(surface, 0, 12); + + prepareScreenType1(); +} + +/** + * Draw Picture + * @remarks Originally called 'dessin' + */ +void MortevielleEngine::drawPicture() { + clearUpperLeftPart(); + if (_caff > 99) { + draw(60, 33); + _screenSurface.drawBox(118, 32, 291, 122, 15); // Medium box + } else if (_caff > 69) { + draw(112, 48); // Heads + _screenSurface.drawBox(222, 47, 155, 92, 15); + } else { + draw(0, 12); + prepareScreenType1(); + if ((_caff < 30) || (_caff > 32)) { + for (int cx = 1; cx <= 6; ++cx) { + if (_openObjects[cx] != 0) + displayAnimFrame(1, _openObjects[cx]); + } + + if (_caff == ATTIC) { + if (_coreVar._atticBallHoleObjectId == 141) + displayAnimFrame(1, 7); + + if (_coreVar._atticRodHoleObjectId == 159) + displayAnimFrame(1, 6); + } else if ((_caff == CELLAR) && (_coreVar._cellarObjectId == 151)) + displayAnimFrame(1, 2); + else if ((_caff == SECRET_PASSAGE) && (_coreVar._secretPassageObjectId == 143)) + displayAnimFrame(1, 1); + else if ((_caff == WELL) && (_coreVar._wellObjectId != 0)) + displayAnimFrame(1, 1); + } + + if (_caff < ROOM26) + startMusicOrSpeech(1); + } +} + +void MortevielleEngine::drawPictureWithText() { + _text.taffich(); + drawPicture(); + _destinationOk = false; +} + +/** + * Engine function - Place + * @remarks Originally called 'tkey1' + */ +void MortevielleEngine::testKey(bool d) { + bool quest = false; + int x, y; + bool click; + + _mouse.hideMouse(); + displayStatusInDescriptionBar('K'); + + // Wait for release from any key or mouse button + while (keyPressed()) + _key = gettKeyPressed(); + + do { + _mouse.getMousePosition(x, y, click); + keyPressed(); + } while (click); + + // Event loop + do { + if (d) + prepareRoom(); + quest = keyPressed(); + _mouse.getMousePosition(x, y, click); + if (shouldQuit()) + return; + } while (!(quest || (click) || (d && _anyone))); + if (quest) + gettKeyPressed(); + setMouseClick(false); + _mouse.showMouse(); +} + +void MortevielleEngine::tlu(int af, int ob) { + _caff = 32; + drawPictureWithText(); + handleDescriptionText(6, ob + 4000); + handleDescriptionText(2, 999); + testKey(true); + _caff = af; + _currMenu = OPCODE_NONE; + _crep = 998; +} + +/** + * Prepare Display Text + * @remarks Originally called 'affrep' + */ +void MortevielleEngine::prepareDisplayText() { + _caff = _coreVar._currPlace; + _crep = _coreVar._currPlace; +} + +/** + * Exit room + * @remarks Originally called 'tsort' + */ +void MortevielleEngine::exitRoom() { + if ((_openObjCount > 0) && (_coreVar._currPlace != OWN_ROOM)) { + if (_coreVar._faithScore < 50) + _coreVar._faithScore += 2; + else + _coreVar._faithScore += (_coreVar._faithScore / 10); + } + + resetOpenObjects(); + + _roomDoorId = OWN_ROOM; + _mchai = 0; + resetRoomVariables(_coreVar._currPlace); +} + +/** + * get 'read' description + * @remarks Originally called 'st4' + */ +void MortevielleEngine::getReadDescription(int objId) { + _crep = 997; + + switch (objId) { + case 114 : + _crep = 109; + break; + case 110 : + _crep = 107; + break; + case 158 : + _crep = 113; + break; + case 152: + case 153: + case 154: + case 155: + case 156: + case 150: + case 100: + case 157: + case 160: + case 161 : + tlu(_caff, objId); + break; + default: + break; + } +} + +/** + * get 'search' description + * @remarks Originally called 'st7' + */ +void MortevielleEngine::getSearchDescription(int objId) { + switch (objId) { + case 116: + case 144: + _crep = 104; + break; + case 126: + case 111: + _crep = 108; + break; + case 132: + _crep = 111; + break; + case 142: + _crep = 112; + break; + default: + _crep = 183; + getReadDescription(objId); + } +} + +void MortevielleEngine::mennor() { + _menu.menuUp(_currMenu); +} + +void MortevielleEngine::premtet() { + draw(10, 80); + _screenSurface.drawBox(18, 79, 155, 92, 15); +} + +void MortevielleEngine::ajchai() { + int cy = kAcha + ((_mchai - 1) * 10) - 1; + int cx = 0; + do { + ++cx; + } while ((cx <= 9) && (_tabdon[cy + cx] != 0)); + + if (_tabdon[cy + cx] == 0) + _tabdon[cy + cx] = _coreVar._selectedObjectId; + else + _crep = 192; +} + +/** + * Check if inventory is full and, if not, add object in it. + * @remarks Originally called 'ajjer' + */ +void MortevielleEngine::addObjectToInventory(int objectId) { + int i = 0; + do { + ++i; + } while ((i <= 5) && (_coreVar._inventory[i] != 0)); + + if (_coreVar._inventory[i] == 0) { + _coreVar._inventory[i] = objectId; + _menu.setInventoryText(); + } else + // Inventory is full + _crep = 139; +} + +/** + * Interact with NPC + * @remarks Originally called 'quelquun' + */ +void MortevielleEngine::interactNPC() { + if (_menu._menuDisplayed) + _menu.eraseMenu(); + + endSearch(); + _crep = 997; +L1: + if (!_hiddenHero) { + if (_crep == 997) + _crep = 138; + handleDescriptionText(2, _crep); + if (_crep == 138) + _speechManager.startSpeech(5, 2, 1); + else + _speechManager.startSpeech(4, 4, 1); + + if (_openObjCount == 0) + _coreVar._faithScore += 2; + else if (_coreVar._faithScore < 50) + _coreVar._faithScore += 4; + else + _coreVar._faithScore += 3 * (_coreVar._faithScore / 10); + exitRoom(); + _menu.setDestinationText(LANDING); + int cx = convertBitIndexToCharacterIndex(_currBitIndex); + _caff = 69 + cx; + _crep = _caff; + _currMenu = MENU_DISCUSS; + _currAction = (_menu._discussMenu[cx]._menuId << 8) | _menu._discussMenu[cx]._actionId; + _syn = true; + _col = true; + } else { + if (getRandomNumber(1, 3) == 2) { + _hiddenHero = false; + _crep = 137; + goto L1; + } else { + handleDescriptionText(2, 136); + int rand = (getRandomNumber(0, 4)) - 2; + _speechManager.startSpeech(3, rand, 1); + clearDescriptionBar(); + displayAloneText(); + resetRoomVariables(MANOR_FRONT); + prepareDisplayText(); + } + } + if (_menu._menuDisplayed) + _menu.drawMenu(); +} + +void MortevielleEngine::tsuiv() { + int tbcl; + int cy = kAcha + ((_mchai - 1) * 10) - 1; + int cx = 0; + do { + ++cx; + ++_searchCount; + int cl = cy + _searchCount; + tbcl = _tabdon[cl]; + } while ((tbcl == 0) && (_searchCount <= 9)); + + if ((tbcl != 0) && (_searchCount < 11)) { + _caff = tbcl; + _crep = _caff + 400; + if (_currBitIndex != 0) + _coreVar._faithScore += 2; + } else { + prepareDisplayText(); + endSearch(); + if (cx > 9) + _crep = 131; + } +} + +/** + * Display Arrow status + * @remarks Originally called 'tfleche' + */ +void MortevielleEngine::displayStatusArrow() { + bool qust; + char touch; + + if (_num == 9999) + return; + + displayStatusInDescriptionBar((unsigned char)152); + bool inRect = false; + do { + touch = '\0'; + + do { + _mouse.moveMouse(qust, touch); + if (shouldQuit()) + return; + + if (getMouseClick()) + inRect = (_mouse._pos.x < 256 * _resolutionScaler) && (_mouse._pos.y < 176) && (_mouse._pos.y > 12); + prepareRoom(); + } while (!(qust || inRect || _anyone)); + + if (qust && (touch == '\103')) + _dialogManager.show(_hintPctMessage, 1); + } while (!((touch == '\73') || ((touch == '\104') && (_x != 0) && (_y != 0)) || (_anyone) || (inRect))); + + if (touch == '\73') + _keyPressedEsc = true; + + if (inRect) { + _x = _mouse._pos.x; + _y = _mouse._pos.y; + } +} + +/** + * Set coordinates + * @remarks Originally called 'tcoord' + */ +void MortevielleEngine::setCoordinates(int sx) { + int sy, ix, iy; + int ib; + + + _num = 0; + _crep = 999; + int a = 0; + int atdon = kAmzon + 3; + int cy = 0; + while (cy < _caff) { + a += _tabdon[atdon]; + atdon += 4; + ++cy; + } + + if (_tabdon[atdon] == 0) { + _crep = 997; + return; + } + + a += kFleche; + int cb = 0; + for (cy = 0; cy <= (sx - 2); ++cy) { + ib = (_tabdon[a + cb] << 8) + _tabdon[(a + cb + 1)]; + cb += (ib * 4) + 2; + } + ib = (_tabdon[a + cb] << 8) + _tabdon[(a + cb + 1)]; + if (ib == 0) { + _crep = 997; + return; + } + + cy = 1; + do { + cb += 2; + sx = _tabdon[a + cb] * _resolutionScaler; + sy = _tabdon[(a + cb + 1)]; + cb += 2; + ix = _tabdon[a + cb] * _resolutionScaler; + iy = _tabdon[(a + cb + 1)]; + ++cy; + } while (!(((_x >= sx) && (_x <= ix) && (_y >= sy) && (_y <= iy)) || (cy > ib))); + + if ((_x >= sx) && (_x <= ix) && (_y >= sy) && (_y <= iy)) { + _num = cy - 1; + return; + } + + _crep = 997; +} + +void MortevielleEngine::treg(int objId) { + int mdes = _caff; + _caff = objId; + + if (((_caff > 29) && (_caff < 33)) || (_caff == 144) || (_caff == 147) || (_caff == 149) || (_currAction == OPCODE_SLOOK)) { + drawPictureWithText(); + if ((_caff > 29) && (_caff < 33)) + handleDescriptionText(2, _caff); + else + handleDescriptionText(2, _caff + 400); + testKey(true); + _caff = mdes; + _currMenu = MENU_NONE; + _crep = 998; + } else { + _obpart = true; + _crep = _caff + 400; + _menu.setSearchMenu(); + } +} + +/** + * Engine function - Put in hand + * @remarks Originally called 'avpoing' + */ +void MortevielleEngine::putInHand(int &objId) { + _crep = 999; + if (_coreVar._selectedObjectId != 0) + addObjectToInventory(_coreVar._selectedObjectId); + + // If inventory wasn't full + if (_crep != 139) { + displayItemInHand(objId + 400); + _coreVar._selectedObjectId = objId; + objId = 0; + } +} + +int MortevielleEngine::rechai() { + int tmpPlace = _coreVar._currPlace; + + if (_coreVar._currPlace == CRYPT) + tmpPlace = CELLAR; + + return _tabdon[kAchai + (tmpPlace * 7) + _num - 1]; +} + +/** + * Check before leaving the secret passage + * @remarks Originally called 't23coul' + */ +int MortevielleEngine::checkLeaveSecretPassage() { + if (!checkInventory(143)) { + _crep = 1512; + loseGame(); + } + + return CELLAR; +} + +/** + * Display status character in description bar + * @remarks Originally called 'fenat' + */ +void MortevielleEngine::displayStatusInDescriptionBar(char stat) { + int color; + + _mouse.hideMouse(); + if (_currGraphicalDevice == MODE_CGA) + color = 2; + else if (_currGraphicalDevice == MODE_HERCULES) + color = 1; + else + color = 12; + + _screenSurface.writeCharacter(Common::Point(306, 193), stat, color); + _screenSurface.drawBox(300, 191, 16, 8, 15); + _mouse.showMouse(); +} + +/** + * Test Keyboard + * @remarks Originally called 'teskbd' + */ +void MortevielleEngine::testKeyboard() { + if (keyPressed()) + gettKeyPressed(); +} + +/** + * Test Key Pressed + * @remarks Originally called 'testou' + */ +int MortevielleEngine::gettKeyPressed() { + char ch = getChar(); + + switch (ch) { + case '\23' : + _soundOff = !_soundOff; + break; + case '\26' : + if ((_x26KeyCount == 1) || (_x26KeyCount == 2)) { + decodeNumber(&_cfiecBuffer[161 * 16], (_cfiecBufferSize - (161 * 16)) / 64); + ++_x26KeyCount; + + return 61; + } + break; + case '\33' : + if (keyPressed()) + ch = getChar(); + break; + default: + break; + } + + return (int)ch; +} + +} // End of namespace Mortevielle |