Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
engines/mortevielle/mortevielle.cpp
engines/mortevielle/metaengine.cpp

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
add_engine mortevielle "Mortevielle" yes "" "" "highres"

View File

@@ -0,0 +1,4 @@
begin_section("Mortevielle");
add_person("Arnaud Boutonné", "Strangerke", "");
add_person("Paul Gilbert", "dreammaster", "");
end_section();

View File

@@ -0,0 +1,55 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "mortevielle/mortevielle.h"
#include "mortevielle/debugger.h"
namespace Mortevielle {
Debugger::Debugger(MortevielleEngine *vm) : GUI::Debugger() {
_vm = vm;
registerCmd("continue", WRAP_METHOD(Debugger, cmdExit));
registerCmd("show_questions", WRAP_METHOD(Debugger, Cmd_showAllQuestions));
registerCmd("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;
}
} // End of namespace Mortevielle

View File

@@ -0,0 +1,47 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#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(MortevielleEngine *vm);
~Debugger() override {}
};
} // End of namespace Mortevielle
#endif

View File

@@ -0,0 +1,68 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "base/plugins.h"
#include "engines/advancedDetector.h"
#include "mortevielle/detection.h"
#include "mortevielle/mortevielle.h"
static const PlainGameDescriptor MortevielleGame[] = {
{"mortevielle", "Mortville Manor"},
{nullptr, nullptr}
};
static const DebugChannelDef debugFlagList[] = {
{Mortevielle::kMortevielleCore, "core", "Core debugging"},
{Mortevielle::kMortevielleGraphics, "graphics", "Graphics debugging"},
DEBUG_CHANNEL_END
};
#include "mortevielle/detection_tables.h"
class MortevielleMetaEngineDetection : public AdvancedMetaEngineDetection<Mortevielle::MortevielleGameDescription> {
public:
MortevielleMetaEngineDetection() : AdvancedMetaEngineDetection(Mortevielle::MortevielleGameDescriptions, MortevielleGame) {
_md5Bytes = 512;
// Use kADFlagUseExtraAsHint to distinguish between original and improved versions
// (i.e. use or not of the game data file).
_flags = kADFlagUseExtraAsHint;
}
const char *getName() const override {
return "mortevielle";
}
const char *getEngineName() const override {
return "Mortville Manor";
}
const char *getOriginalCopyright() const override {
return "Mortville Manor (C) 1987-89 Lankhor";
}
const DebugChannelDef *getDebugChannels() const override {
return debugFlagList;
}
};
REGISTER_PLUGIN_STATIC(MORTEVIELLE_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, MortevielleMetaEngineDetection);

View File

@@ -0,0 +1,46 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MORTEVIELLE_DETECTION_H
#define MORTEVIELLE_DETECTION_H
#include "engines/advancedDetector.h"
namespace Mortevielle {
#define GAMEOPTION_TTS GUIO_GAMEOPTIONS1
enum {
kUseOriginalData = 0,
kUseEngineDataFile = 1
};
struct MortevielleGameDescription {
AD_GAME_DESCRIPTION_HELPERS(desc);
ADGameDescription desc;
Common::Language originalLanguage;
uint8 dataFeature;
};
} // End of namespace Mortevielle
#endif // MORTEVIELLE_DETECTION_H

View File

@@ -0,0 +1,114 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
namespace Mortevielle {
static const MortevielleGameDescription MortevielleGameDescriptions[] = {
// French
{
{
"mortevielle",
"",
AD_ENTRY2s("menufr.mor", "e413f36b9e14eef16130adc347a9391f", 144,
"dxx.mor", "949e68e829ecd5ad29e36a00347a9e7e", 207744),
Common::FR_FRA,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO2(GUIO_NOMIDI, GAMEOPTION_TTS)
}, Common::FR_FRA, kUseOriginalData
},
// German
{
{
"mortevielle",
"",
AD_ENTRY2s("menual.mor", "792aea282b07a1d74c4a4abeabc90c19", 144,
"dxx.mor", "949e68e829ecd5ad29e36a00347a9e7e", 207744),
Common::DE_DEU,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO2(GUIO_NOMIDI, GAMEOPTION_TTS)
}, Common::DE_DEU, kUseOriginalData
},
// German, improved translation
// {
// {
// "mortevielle",
// "Improved Translation",
// AD_ENTRY2s("menual.mor", "792aea282b07a1d74c4a4abeabc90c19", 144,
// "dxx.mor", "949e68e829ecd5ad29e36a00347a9e7e", 207744),
// Common::DE_DEU,
// Common::kPlatformDOS,
// ADGF_NO_FLAGS,
// GUIO2(GUIO_NOMIDI, GAMEOPTION_TTS)
// }, Common::DE_DEU, kUseEngineDataFile
// },
// DOS English version doesn't exist. Technically, they are French or German versions,
// using English strings stored mort.dat
// English on top of French version
{
{
"mortevielle",
"",
AD_ENTRY2s("menufr.mor", "e413f36b9e14eef16130adc347a9391f", 144,
"dxx.mor", "949e68e829ecd5ad29e36a00347a9e7e", 207744),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO2(GUIO_NOMIDI, GAMEOPTION_TTS)
}, Common::FR_FRA, kUseEngineDataFile
},
// English on top of German version
{
{
"mortevielle",
"",
AD_ENTRY2s("menual.mor", "792aea282b07a1d74c4a4abeabc90c19", 144,
"dxx.mor", "949e68e829ecd5ad29e36a00347a9e7e", 207744),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO2(GUIO_NOMIDI, GAMEOPTION_TTS)
}, Common::DE_DEU, kUseEngineDataFile
},
// French, provided by ultrapingu in bug ref #6575
{
{
"mortevielle",
"",
AD_ENTRY2s("menu.mor", "3fef0a3f8fca99fdcb6dbca8cbcef46f", 160,
"dxx.mor", "949e68e829ecd5ad29e36a00347a9e7e", 207744),
Common::FR_FRA,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO2(GUIO_NOMIDI, GAMEOPTION_TTS)
}, Common::FR_FRA, kUseEngineDataFile
},
{ AD_TABLE_END_MARKER , Common::EN_ANY, kUseEngineDataFile}
};
} // End of namespace Mortevielle

View File

@@ -0,0 +1,475 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
/*
* 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 "common/str.h"
namespace Mortevielle {
DialogManager::DialogManager(MortevielleEngine *vm) {
_vm = vm;
}
/**
* Alert function - Show
* @remarks Originally called 'do_alert'
*/
int DialogManager::show(const Common::String &msg) {
// 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);
Common::Point curPos;
if (alertStr == "") {
drawAlertBox(10, 5, colNumb);
} else {
drawAlertBox(8, 7, colNumb);
int i = -1;
_vm->_screenSurface->_textPos.y = 70;
do {
curPos.x = 320;
Common::String displayStr = "";
while ((alertStr[i + 1] != '|') && (alertStr[i + 1] != ']')) {
++i;
displayStr += alertStr[i];
curPos.x -= 3;
}
_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) * kResolutionScaler;
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) * kResolutionScaler;
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 = 1;
setPosition(1, coldep, esp);
Common::String tmp4(" ");
tmp4 += buttonStr[1];
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);
col *= 6;
}
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 firstLine, int lineNum, int width) {
if (width > 640)
width = 640;
int x = 320 - ((uint)width / 2);
int y = (firstLine - 1) * 8;
int xx = x + width;
int yy = y + (lineNum * 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;
int correctCount = 0;
for (int indx = 0; indx < 10; ++indx) {
_vm->_mouse->hideMouse();
_vm->clearScreen();
_vm->_mouse->showMouse();
int 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;
int 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 * kResolutionScaler, 27 + j * 8, (maxLength * 3 + 55) * kResolutionScaler, 34 + j * 8);
coor[j]._enabled = true;
while ((int)choiceArray[j].size() < maxLength) {
choiceArray[j] += ' ';
}
}
coor[lastOption - firstOption + 2]._enabled = false;
int 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) + 4, 16, 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->_soundManager->startSpeech(SpeechNum, 0, 0);
_vm->_key = waitForF3F8();
if (_vm->shouldQuit())
return;
} while (_vm->_key != 66); // keycode for F8
// just stop the speech when pressing F8
if (_vm->_soundManager->_ttsMan != nullptr)
_vm->_soundManager->_ttsMan->stop();
}
/**
* 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 = false;
_vm->handleDescriptionText(2, kDialogStringIndex + 143);
}
} // End of namespace Mortevielle

View File

@@ -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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
/*
* 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 firstLine, int lineNum, int width);
void setButtonText(Common::String c, int coldep, int nbcase, Common::String *str, int esp);
public:
DialogManager(MortevielleEngine *vm);
int show(const Common::String &msg);
void drawF3F8();
void checkForF8(int SpeechNum, bool drawFrame2Fl);
int waitForF3F8();
void displayIntroScreen(bool drawFrame2Fl);
void displayIntroFrame2();
bool showKnowledgeCheck();
};
} // End of namespace Mortevielle
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
/*
* 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:
ScreenSurface(MortevielleEngine *vm);
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);
// TODO: Refactor code to remove this method, for increased performance
void setPixel(const Common::Point &pt, int palIndex);
};
} // End of namespace Mortevielle
#endif

View File

@@ -0,0 +1,809 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
/*
* 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}
};
Menu::Menu(MortevielleEngine *vm) {
_vm = vm;
_opcodeAttach = _opcodeWait = _opcodeForce = _opcodeSleep = OPCODE_NONE;
_opcodeListen = _opcodeEnter = _opcodeClose = _opcodeSearch = OPCODE_NONE;
_opcodeKnock = _opcodeScratch = _opcodeRead = _opcodeEat = OPCODE_NONE;
_opcodePlace = _opcodeOpen = _opcodeTake = _opcodeLook = OPCODE_NONE;
_opcodeSmell = _opcodeSound = _opcodeLeave = _opcodeLift = OPCODE_NONE;
_opcodeTurn = _opcodeSHide = _opcodeSSearch = _opcodeSRead = OPCODE_NONE;
_opcodeSPut = _opcodeSLook = _msg3 = _msg4 = OPCODE_NONE;
_menuActive = false;
_menuSelected = false;
_multiTitle = false;
_menuDisplayed = false;
for (int i = 0; i < 9; i++) {
_discussMenu[i]._menuId = MENU_NONE;
_discussMenu[i]._actionId = 0;
_inventoryMenu[i]._menuId = MENU_NONE;
_inventoryMenu[i]._actionId = 0;
}
for (int i = 0; i < 8; i++) {
_moveMenu[i]._menuId = MENU_NONE;
_moveMenu[i]._actionId = 0;
}
for (int i = 0; i < 12; i++) {
_actionMenu[i]._menuId = MENU_NONE;
_actionMenu[i]._actionId = 0;
}
}
void Menu::readVerbNums(Common::File &f, int dataSize) {
// Figure out what language Id is needed
byte desiredLanguageId;
switch(_vm->getLanguage()) {
case Common::EN_ANY:
desiredLanguageId = MORTDAT_LANG_ENGLISH;
break;
case Common::FR_FRA:
desiredLanguageId = MORTDAT_LANG_FRENCH;
break;
case Common::DE_DEU:
desiredLanguageId = MORTDAT_LANG_GERMAN;
break;
default:
warning("Language not supported, switching to English");
desiredLanguageId = MORTDAT_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;
}
assert(dataSize == 52);
_opcodeAttach = f.readUint16LE();
_opcodeWait = f.readUint16LE();
_opcodeForce = f.readUint16LE();
_opcodeSleep = f.readUint16LE();
_opcodeListen = f.readUint16LE();
_opcodeEnter = f.readUint16LE();
_opcodeClose = f.readUint16LE();
_opcodeSearch = f.readUint16LE();
_opcodeKnock = f.readUint16LE();
_opcodeScratch = f.readUint16LE();
_opcodeRead = f.readUint16LE();
_opcodeEat = f.readUint16LE();
_opcodePlace = f.readUint16LE();
_opcodeOpen = f.readUint16LE();
_opcodeTake = f.readUint16LE();
_opcodeLook = f.readUint16LE();
_opcodeSmell = f.readUint16LE();
_opcodeSound = f.readUint16LE();
_opcodeLeave = f.readUint16LE();
_opcodeLift = f.readUint16LE();
_opcodeTurn = f.readUint16LE();
_opcodeSHide = f.readUint16LE();
_opcodeSSearch = f.readUint16LE();
_opcodeSRead = f.readUint16LE();
_opcodeSPut = f.readUint16LE();
_opcodeSLook = f.readUint16LE();
_actionMenu[0]._menuId = OPCODE_NONE >> 8;
_actionMenu[0]._actionId = OPCODE_NONE & 0xFF;
_actionMenu[1]._menuId = _opcodeSHide >> 8;
_actionMenu[1]._actionId = _opcodeSHide & 0xFF;
_actionMenu[2]._menuId = _opcodeAttach >> 8;
_actionMenu[2]._actionId = _opcodeAttach & 0xFF;
_actionMenu[3]._menuId = _opcodeForce >> 8;
_actionMenu[3]._actionId = _opcodeForce & 0xFF;
_actionMenu[4]._menuId = _opcodeSleep >> 8;
_actionMenu[4]._actionId = _opcodeSleep & 0xFF;
_actionMenu[5]._menuId = _opcodeEnter >> 8;
_actionMenu[5]._actionId = _opcodeEnter & 0xFF;
_actionMenu[6]._menuId = _opcodeClose >> 8;
_actionMenu[6]._actionId = _opcodeClose & 0xFF;
_actionMenu[7]._menuId = _opcodeKnock >> 8;
_actionMenu[7]._actionId = _opcodeKnock & 0xFF;
_actionMenu[8]._menuId = _opcodeEat >> 8;
_actionMenu[8]._actionId = _opcodeEat & 0xFF;
_actionMenu[9]._menuId = _opcodePlace >> 8;
_actionMenu[9]._actionId = _opcodePlace & 0xFF;
_actionMenu[10]._menuId = _opcodeOpen >> 8;
_actionMenu[10]._actionId = _opcodeOpen & 0xFF;
_actionMenu[11]._menuId = _opcodeLeave >> 8;
_actionMenu[11]._actionId = _opcodeLeave & 0xFF;
}
/**
* Setup a menu's contents
* @remarks Originally called 'menut'
*/
void Menu::setText(MenuItem item, Common::String name) {
Common::String s = name;
switch (item._menuId) {
case MENU_INVENTORY:
if (item._actionId != 7) {
while (s.size() < 22)
s += ' ';
_inventoryStringArray[item._actionId] = s;
_inventoryStringArray[item._actionId].insertChar(' ', 0);
}
break;
case MENU_MOVE: {
// If the first character isn't '*' or ' ' then it's missing a heading space
char c = s[0];
if (c != '*' && c != ' ')
s.insertChar(' ', 0);
while (s.size() < 22)
s += ' ';
_moveStringArray[item._actionId] = s;
}
break;
case MENU_ACTION: {
// If the first character isn't '*' or ' ' then it's missing a heading space
char c = s[0];
if (c != '*' && c != ' ')
s.insertChar(' ', 0);
while (s.size() < 10)
s += ' ';
_actionStringArray[item._actionId] = s;
}
break;
case MENU_SELF: {
// If the first character isn't '*' or ' ' then it's missing a heading space
char c = s[0];
if (c != '*' && c != ' ')
s.insertChar(' ', 0);
while (s.size() < 10)
s += ' ';
_selfStringArray[item._actionId] = s;
}
break;
case MENU_DISCUSS:
_discussStringArray[item._actionId] = s;
break;
default:
break;
}
}
/**
* Init destination menu
* @remarks Originally called 'tmlieu'
*/
void Menu::setDestinationText(int roomId) {
Common::String nomp;
if (roomId == ROOM26)
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], nomp);
}
nomp = "* ";
for (int i = 7; i >= destinationId + 1; --i)
setText(_moveMenu[i], nomp);
}
/**
* _disable a menu item
* @param menuId Menu number
* @param actionId Item index
*/
void Menu::disableMenuItem(MenuItem item) {
switch (item._menuId) {
case MENU_INVENTORY:
if (item._actionId > 6) {
_inventoryStringArray[item._actionId].setChar('<', 0);
_inventoryStringArray[item._actionId].setChar('>', 21);
} else
_inventoryStringArray[item._actionId].setChar('*', 0);
break;
case MENU_MOVE:
_moveStringArray[item._actionId].setChar('*', 0);
break;
case MENU_ACTION:
_actionStringArray[item._actionId].setChar('*', 0);
break;
case MENU_SELF:
_selfStringArray[item._actionId].setChar('*', 0);
break;
case MENU_DISCUSS:
_discussStringArray[item._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(MenuItem item) {
switch (item._menuId) {
case MENU_INVENTORY:
_inventoryStringArray[item._actionId].setChar(' ', 0);
_inventoryStringArray[item._actionId].setChar(' ', 21);
break;
case MENU_MOVE:
_moveStringArray[item._actionId].setChar(' ', 0);
break;
case MENU_ACTION:
_actionStringArray[item._actionId].setChar(' ', 0);
break;
case MENU_SELF:
_selfStringArray[item._actionId].setChar(' ', 0);
break;
case MENU_DISCUSS:
_discussStringArray[item._actionId].setChar(' ', 0);
break;
default:
break;
}
}
void Menu::displayMenu() {
_vm->_mouse->hideMouse();
_vm->_screenSurface->fillRect(7, Common::Rect(0, 0, 639, 10));
int col = 28 * kResolutionScaler;
for (int charNum = 0; charNum < 6; charNum++) {
// One character after the other
int idx = 0;
for (int y = 1; y < 9; ++y) {
// One column after the other
int x = col;
for (int k = 0; k < 3; ++k) {
// One line after the other
uint msk = 0x80;
for (int pt = 0; pt <= 7; ++pt) {
if ((_charArr[charNum][idx] & 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), 9);
}
msk >>= 1;
++x;
}
++idx;
}
}
col += 48 * kResolutionScaler;
}
_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) * kResolutionScaler;
int charWidth = 6;
int xmx = dxcar * charWidth + xmn + 2;
if ((pos.x > xmn) && (pos.x < xmx) && (pos.y < ymx) && (pos.y > 15)) {
int 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) {
// Make a copy of the current screen surface for later restore
_vm->_backgroundSurface.copyFrom(*_vm->_screenSurface);
// Draw the menu
int minX = menuConstants[ii - 1][0] << 3;
int lineNum = menuConstants[ii - 1][3];
_vm->_mouse->hideMouse();
int deltaX = 6;
int maxX = minX + (menuConstants[ii - 1][2] * deltaX) + 6;
if ((ii == 4) && (_vm->getLanguage() == Common::EN_ANY))
// Extra width needed for Self menu in English version
maxX = 435;
_vm->_screenSurface->fillRect(15, Common::Rect(minX, 12, maxX, 10 + (menuConstants[ii - 1][1] << 1)));
_vm->_screenSurface->fillRect(0, Common::Rect(maxX, 12, maxX + 4, 10 + (menuConstants[ii - 1][1] << 1)));
_vm->_screenSurface->fillRect(0, Common::Rect(minX, 8 + (menuConstants[ii - 1][1] << 1), maxX + 4, 12 + (menuConstants[ii - 1][1] << 1)));
_vm->_screenSurface->putxy(minX, 16);
for (int i = 1; i <= lineNum; i++) {
switch (ii) {
case 1:
if (_inventoryStringArray[i][0] != '*')
_vm->_screenSurface->drawString(_inventoryStringArray[i], 4);
break;
case 2:
if (_moveStringArray[i][0] != '*')
_vm->_screenSurface->drawString(_moveStringArray[i], 4);
break;
case 3:
if (_actionStringArray[i][0] != '*')
_vm->_screenSurface->drawString(_actionStringArray[i], 4);
break;
case 4:
if (_selfStringArray[i][0] != '*')
_vm->_screenSurface->drawString(_selfStringArray[i], 4);
break;
case 5:
if (_discussStringArray[i][0] != '*')
_vm->_screenSurface->drawString(_discussStringArray[i], 4);
break;
case 6:
_vm->_screenSurface->drawString(_vm->getEngineString(S_SAVE_LOAD + i), 4);
break;
case 7: {
Common::String s = _vm->getEngineString(S_SAVE_LOAD + 1);
s += ' ';
s += (char)(48 + i);
_vm->_screenSurface->drawString(s, 4);
}
break;
case 8:
if (i == 1)
_vm->_screenSurface->drawString(_vm->getEngineString(S_RESTART), 4);
else {
Common::String s = _vm->getEngineString(S_SAVE_LOAD + 2);
s += ' ';
s += (char)(47 + i);
_vm->_screenSurface->drawString(s, 4);
}
break;
default:
break;
}
_vm->_screenSurface->putxy(minX, _vm->_screenSurface->_textPos.y + 8);
}
_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.getPixels();
// 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 * kResolutionScaler) && curPos.x <= (28 * kResolutionScaler + 24))
|| (curPos.x >= (76 * kResolutionScaler) && curPos.x <= (76 * kResolutionScaler + 24))
|| ((curPos.x > 124 * kResolutionScaler) && (curPos.x < 124 * kResolutionScaler + 24))
|| ((curPos.x > 172 * kResolutionScaler) && (curPos.x < 172 * kResolutionScaler + 24))
|| ((curPos.x > 220 * kResolutionScaler) && (curPos.x < 220 * kResolutionScaler + 24))
|| ((curPos.x > 268 * kResolutionScaler) && (curPos.x < 268 * kResolutionScaler + 24)));
if (tes) {
int ix;
if (curPos.x < 76 * kResolutionScaler)
ix = MENU_INVENTORY;
else if (curPos.x < 124 * kResolutionScaler)
ix = MENU_MOVE;
else if (curPos.x < 172 * kResolutionScaler)
ix = MENU_ACTION;
else if (curPos.x < 220 * kResolutionScaler)
ix = MENU_SELF;
else if (curPos.x < 268 * kResolutionScaler)
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() {
Common::File f;
bool menuLoaded = false;
// First try to read it from mort.dat if useOriginalData() is false
if (!_vm->useOriginalData()) {
if (!f.open(MORT_DAT))
warning("File %s not found. Using default menu from game data", MORT_DAT);
else {
// Figure out what language Id is needed
byte desiredLanguageId;
switch(_vm->getLanguage()) {
case Common::EN_ANY:
desiredLanguageId = MORTDAT_LANG_ENGLISH;
break;
case Common::FR_FRA:
desiredLanguageId = MORTDAT_LANG_FRENCH;
break;
case Common::DE_DEU:
desiredLanguageId = MORTDAT_LANG_GERMAN;
break;
default:
warning("Language not supported, switching to English");
desiredLanguageId = MORTDAT_LANG_ENGLISH;
break;
}
// Validate the data file header
char fileId[4];
f.read(fileId, 4);
// Do not display warnings here. They would already have been displayed in MortevielleEngine::loadMortDat().
if (strncmp(fileId, "MORT", 4) == 0 && f.readByte() >= MORT_DAT_REQUIRED_VERSION) {
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, "MENU", 4)) {
// 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);
continue;
}
if (dataSize == 6 * 24) {
f.read(_charArr, dataSize);
menuLoaded = true;
} else
warning("Wrong size %d for menu data. Expected %d or less", dataSize, 6 * 24);
break;
} else {
// Other sections
f.skip(dataSize);
}
}
}
// Close the file
f.close();
if (!menuLoaded)
warning("Failed to load menu from mort.dat. Will use default menu from game data instead.");
}
}
if (!menuLoaded) {
// Load menu from game data using the original language
if (_vm->getOriginalLanguage() == Common::FR_FRA) {
// We do not support reading menu.mor (it has a different format).
// If the game version does not have menufr.mor it has to get the menu from the DAT file.
if (!f.open("menufr.mor"))
error("Missing file - menufr.mor");
} else { // Common::DE_DEU
if (!f.open("menual.mor"))
error("Missing file - menual.mor");
}
f.read(_charArr, 6 * 24);
f.close();
}
// Skipped: dialog asking to swap floppy
for (int i = 1; i <= 8; ++i)
_inventoryStringArray[i] = "* ";
_inventoryStringArray[7] = "< -*-*-*-*-*-*-*-*-*- ";
for (int i = 1; i <= 7; ++i)
_moveStringArray[i] = "* ";
for (int i = 1; i < 22; i++) {
_actionStringArray[i] = _vm->getString(i + kMenuActionStringIndex);
if ((_actionStringArray[i][0] != '*') && (_actionStringArray[i][0] != ' '))
_actionStringArray[i].insertChar(' ', 0);
while (_actionStringArray[i].size() < 10)
_actionStringArray[i] += ' ';
if (i < 9) {
if (i < 6) {
_selfStringArray[i] = _vm->getString(i + kMenuSelfStringIndex);
if ((_selfStringArray[i][0] != '*') && (_selfStringArray[i][0] != ' '))
_selfStringArray[i].insertChar(' ', 0);
while (_selfStringArray[i].size() < 10)
_selfStringArray[i] += ' ';
}
_discussStringArray[i] = _vm->getString(i + kMenuSayStringIndex) + ' ';
}
}
for (int 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]);
}
_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(_moveMenu[i]);
for (int i = 1; i <= 11; ++i)
disableMenuItem(_actionMenu[i]);
MenuItem miSound;
miSound._menuId = _opcodeSound >> 8;
miSound._actionId = _opcodeSound & 0xFF;
MenuItem miLift;
miLift._menuId = _opcodeLift >> 8;
miLift._actionId = _opcodeLift & 0xFF;
setText(miSound, _vm->getEngineString(S_SUITE));
setText(miLift, _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]);
MenuItem miSound;
miSound._menuId = _opcodeSound >> 8;
miSound._actionId = _opcodeSound & 0xFF;
MenuItem miLift;
miLift._menuId = _opcodeLift >> 8;
miLift._actionId = _opcodeLift & 0xFF;
setText(miSound, _vm->getEngineString(S_PROBE));
setText(miLift, _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], nomp);
enableMenuItem(_inventoryMenu[i]);
}
}
if (cy < 6) {
for (int i = cy + 1; i <= 6; ++i) {
setText(_inventoryMenu[i], " ");
disableMenuItem(_inventoryMenu[i]);
}
}
}
} // End of namespace Mortevielle

123
engines/mortevielle/menu.h Normal file
View File

@@ -0,0 +1,123 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
/*
* 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
};
const int OPCODE_NONE = 0;
struct MenuItem {
int _menuId;
int _actionId;
};
class Menu {
private:
MortevielleEngine *_vm;
byte _charArr[6][24];
int _msg3;
int _msg4;
void util(Common::Point pos);
void invert(int indx);
void menuDown(int ii);
public:
Menu(MortevielleEngine *vm);
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];
int _opcodeAttach;
int _opcodeWait;
int _opcodeForce;
int _opcodeSleep;
int _opcodeListen;
int _opcodeEnter;
int _opcodeClose;
int _opcodeSearch;
int _opcodeKnock;
int _opcodeScratch;
int _opcodeRead;
int _opcodeEat;
int _opcodePlace;
int _opcodeOpen;
int _opcodeTake;
int _opcodeLook;
int _opcodeSmell;
int _opcodeSound;
int _opcodeLeave;
int _opcodeLift;
int _opcodeTurn;
int _opcodeSHide;
int _opcodeSSearch;
int _opcodeSRead;
int _opcodeSPut;
int _opcodeSLook;
MenuItem _actionMenu[12];
void readVerbNums(Common::File &f, int dataSize);
void setText(MenuItem item, Common::String name);
void setDestinationText(int roomId);
void setInventoryText();
void disableMenuItem(MenuItem item);
void enableMenuItem(MenuItem item);
void displayMenu();
void drawMenu();
void menuUp(int msgId);
void eraseMenu();
void updateMenu();
void initMenu();
void setSearchMenu();
void unsetSearchMenu();
};
} // End of namespace Mortevielle
#endif

View File

@@ -0,0 +1,123 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "base/plugins.h"
#include "engines/advancedDetector.h"
#include "common/translation.h"
#include "mortevielle/mortevielle.h"
#include "mortevielle/saveload.h"
#include "mortevielle/detection.h"
namespace Mortevielle {
#ifdef USE_TTS
static const ADExtraGuiOptionsMap optionsList[] = {
{
GAMEOPTION_TTS,
{
_s("Enable Text to Speech"),
_s("Use TTS to read text in the game (if TTS is available)"),
"tts_enabled",
false,
0,
0
}
},
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
#endif
uint32 MortevielleEngine::getGameFlags() const { return _gameDescription->desc.flags; }
Common::Language MortevielleEngine::getLanguage() const { return _gameDescription->desc.language; }
Common::Language MortevielleEngine::getOriginalLanguage() const { return _gameDescription->originalLanguage; }
bool MortevielleEngine::useOriginalData() const { return _gameDescription->dataFeature == kUseOriginalData; }
} // End of namespace Mortevielle
class MortevielleMetaEngine : public AdvancedMetaEngine<Mortevielle::MortevielleGameDescription> {
public:
const char *getName() const override {
return "mortevielle";
}
#ifdef USE_TTS
const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override {
return Mortevielle::optionsList;
}
#endif
Common::Error createInstance(OSystem *syst, Engine **engine, const Mortevielle::MortevielleGameDescription *desc) const override;
bool hasFeature(MetaEngineFeature f) const override;
int getMaximumSaveSlot() const override;
SaveStateList listSaves(const char *target) const override;
SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override;
Common::String getSavegameFile(int saveGameIdx, const char *target) const override {
if (!target)
target = getName();
if (saveGameIdx == kSavegameFilePattern)
return Common::String::format("%s.###", target); // There is also sav0.mor for slot 0
else
return Mortevielle::MortevielleEngine::generateSaveFilename(target, saveGameIdx);
}
};
Common::Error MortevielleMetaEngine::createInstance(OSystem *syst, Engine **engine, const Mortevielle::MortevielleGameDescription *desc) const {
*engine = new Mortevielle::MortevielleEngine(syst,desc);
return Common::kNoError;
}
bool MortevielleMetaEngine::hasFeature(MetaEngineFeature f) const {
switch (f) {
case kSupportsListSaves:
case kSupportsDeleteSave:
case kSupportsLoadingDuringStartup:
case kSavesSupportMetaInfo:
case kSavesSupportThumbnail:
case kSavesSupportCreationDate:
case kSimpleSavesNames:
return true;
default:
return false;
}
}
int MortevielleMetaEngine::getMaximumSaveSlot() const { return 99; }
SaveStateList MortevielleMetaEngine::listSaves(const char *target) const {
return Mortevielle::SavegameManager::listSaves(this, target);
}
SaveStateDescriptor MortevielleMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
Common::String filename = Mortevielle::MortevielleEngine::generateSaveFilename(target, slot);
return Mortevielle::SavegameManager::querySaveMetaInfos(this, filename);
}
#if PLUGIN_ENABLED_DYNAMIC(MORTEVIELLE)
REGISTER_PLUGIN_DYNAMIC(MORTEVIELLE, PLUGIN_TYPE_ENGINE, MortevielleMetaEngine);
#else
REGISTER_PLUGIN_STATIC(MORTEVIELLE, PLUGIN_TYPE_ENGINE, MortevielleMetaEngine);
#endif

View File

@@ -0,0 +1,26 @@
MODULE := engines/mortevielle
MODULE_OBJS := \
actions.o \
debugger.o \
dialogs.o \
graphics.o \
menu.o \
metaengine.o \
mortevielle.o \
mouse.o \
outtext.o \
saveload.o \
sound.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
# Detection objects
DETECT_OBJS += $(MODULE)/detection.o

View File

@@ -0,0 +1,510 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
/*
* 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 "common/translation.h"
#include "engines/util.h"
#include "engines/engine.h"
#include "graphics/pixelformat.h"
namespace Mortevielle {
MortevielleEngine *g_vm;
MortevielleEngine::MortevielleEngine(OSystem *system, const MortevielleGameDescription *gameDesc):
Engine(system), _gameDescription(gameDesc), _randomSource("mortevielle") {
g_vm = this;
setDebugger(new Debugger(this));
_dialogManager = new DialogManager(this);
_screenSurface = new ScreenSurface(this);
_mouse = new MouseHandler(this);
_text = new TextHandler(this);
_soundManager = new SoundManager(this, _mixer);
_savegameManager = new SavegameManager(this);
_menu = new Menu(this);
_lastGameFrame = 0;
_mouseClick = false;
_inMainGameLoop = false;
_quitGame = false;
_pauseStartTime = -1;
_roomPresenceLuc = false;
_roomPresenceIda = false;
_purpleRoomPresenceLeo = false;
_roomPresenceGuy = false;
_roomPresenceEva = false;
_roomPresenceMax = false;
_roomPresenceBob = false;
_roomPresencePat = false;
_toiletsPresenceBobMax = false;
_bathRoomPresenceBobMax = false;
_juliaRoomPresenceLeo = false;
_soundOff = false;
_largestClearScreen = false;
_hiddenHero = false;
_heroSearching = false;
_keyPressedEsc = false;
_reloadCFIEC = false;
_outsideOnlyFl = true;
_col = false;
_syn = false;
_obpart = false;
_destinationOk = false;
_anyone = false;
_uptodatePresence = false;
_textColor = 0;
_place = -1;
_x26KeyCount = -1;
_caff = -1;
_day = 0;
_curPict = nullptr;
_curAnim = nullptr;
_rightFramePict = nullptr;
resetCoreVar();
_maff = 0;
_crep = 0;
_minute = 0;
_curSearchObjId = 0;
_controlMenu = 0;
_startTime = 0;
_endTime = 0;
_roomDoorId = OWN_ROOM;
_openObjCount = 0;
_takeObjCount = 0;
_num = 0;
_searchCount = 0;
_introSpeechPlayed = false;
_inGameHourDuration = 0;
_x = 0;
_y = 0;
_currentHourCount = 0;
_currentTime = 0;
_cfiecBuffer = nullptr;
_cfiecBufferSize = 0;
for (int i = 0; i < 601; i++) {
_dialogHintArray[i]._hintId = 0;
_dialogHintArray[i]._point = 0;
}
_currMenu = OPCODE_NONE;
_currAction = OPCODE_NONE;
_menuOpcode = OPCODE_NONE;
_addFix = 0;
_currBitIndex = 0;
_currDay = 0;
_currHour = 10;
_currHalfHour = 0;
_hour = 10;
_key = 0;
_manorDistance = 0;
_numpal = 0;
_savedBitIndex = 0;
_endGame = false;
_loseGame = false;
_txxFileFl = false;
_is = 0;
}
MortevielleEngine::~MortevielleEngine() {
delete _menu;
delete _savegameManager;
delete _soundManager;
delete _text;
delete _mouse;
delete _screenSurface;
delete _dialogManager;
free(_curPict);
free(_curAnim);
free(_rightFramePict);
}
/**
* Specifies whether the engine supports given features
*/
bool MortevielleEngine::hasFeature(EngineFeature f) const {
return
(f == kSupportsReturnToLauncher) ||
(f == kSupportsLoadingDuringRuntime) ||
(f == kSupportsSavingDuringRuntime);
}
/**
* Return true if a game can currently be loaded
*/
bool MortevielleEngine::canLoadGameStateCurrently(Common::U32String *msg) {
// Saving is only allowed in the main game event loop
return _inMainGameLoop;
}
/**
* Return true if a game can currently be saved
*/
bool MortevielleEngine::canSaveGameStateCurrently(Common::U32String *msg) {
// 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, bool isAutosave) {
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);
}
/**
* Pause the game.
*/
void MortevielleEngine::pauseEngineIntern(bool pause) {
Engine::pauseEngineIntern(pause);
if (pause) {
if (_pauseStartTime == -1)
_pauseStartTime = readclock();
} else {
if (_pauseStartTime != -1) {
int pauseEndTime = readclock();
_currentTime += (pauseEndTime - _pauseStartTime);
if (_uptodatePresence)
_startTime += (pauseEndTime - _pauseStartTime);
}
_pauseStartTime = -1;
}
}
/**
* Initialize the game state
*/
Common::ErrorCode MortevielleEngine::initialize() {
// Initialize graphics mode
initGraphics(SCREEN_WIDTH, SCREEN_HEIGHT);
// Set up an intermediate screen surface
_screenSurface->create(SCREEN_WIDTH, SCREEN_HEIGHT, Graphics::PixelFormat::createFormatCLUT8());
_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();
loadPalette();
loadCFIPH();
loadCFIEC();
decodeNumber(&_cfiecBuffer[161 * 16], (_cfiecBufferSize - (161 * 16)) / 64);
_x26KeyCount = 1;
initMaxAnswer();
initMouse();
loadPlaces();
_soundOff = false;
_largestClearScreen = false;
testKeyboard();
showConfigScreen();
testKeyboard();
clearScreen();
_soundManager->loadNoise();
_soundManager->loadAmbiantSounds();
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)) {
GUIErrorMessageFormat(_("Unable to locate the '%s' engine data file."), MORT_DAT);
return Common::kReadingFailed;
}
// Validate the data file header
char fileId[4];
f.read(fileId, 4);
if (strncmp(fileId, "MORT", 4) != 0) {
GUIErrorMessageFormat(_("The '%s' engine data file is corrupt."), MORT_DAT);
return Common::kReadingFailed;
}
// Check the version
int majVer = f.readByte();
int minVer = f.readByte();
if (majVer < MORT_DAT_REQUIRED_VERSION) {
GUIErrorMessageFormat(
_("Incorrect version of the '%s' engine data file found. Expected %d.%d but got %d.%d."),
MORT_DAT, MORT_DAT_REQUIRED_VERSION, 0, majVer, minVer);
return Common::kReadingFailed;
}
// 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 if (!strncmp(dataType, "VERB", 4)) {
_menu->readVerbNums(f, dataSize);
} 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 = MORTDAT_LANG_ENGLISH;
break;
case Common::FR_FRA:
desiredLanguageId = MORTDAT_LANG_FRENCH;
break;
case Common::DE_DEU:
desiredLanguageId = MORTDAT_LANG_GERMAN;
break;
default:
warning("Language not supported, switching to English");
desiredLanguageId = MORTDAT_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();
else {
_caff = 51;
_text->taffich();
}
// Either load the initial game state savegame, or the specified savegame number
adzon();
resetVariables();
if (loadSlot != 0)
_savegameManager->loadSavegame(getSaveStateName(loadSlot));
// Run the main game loop
mainGame();
// Cleanup (allocated in initialize())
_screenSurface->free();
free(_soundManager->_cfiphBuffer);
free(_cfiecBuffer);
return Common::kNoError;
}
/**
* Show the game introduction
*/
void MortevielleEngine::showIntroduction() {
_dialogManager->displayIntroScreen(false);
_dialogManager->checkForF8(142, false);
if (shouldQuit())
return;
_dialogManager->displayIntroFrame2();
_dialogManager->checkForF8(143, true);
if (shouldQuit())
return;
showTitleScreen();
music();
_mixer->stopAll();
}
/**
* 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);
_menu->initMenu();
charToHour();
initGame();
clearScreen();
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

View File

@@ -0,0 +1,486 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
/*
* 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/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/outtext.h"
#include "mortevielle/detection.h"
namespace Mortevielle {
// Debug channels
enum {
kMortevielleCore = 1,
kMortevielleGraphics,
kMortevielleSounds,
};
// Game languages
enum {
MORTDAT_LANG_FRENCH = 0,
MORTDAT_LANG_ENGLISH = 1,
MORTDAT_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_OKAY = 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)
#define DISK_ACCESS_DELAY 1000
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 kAsearch = 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 kStartingScreenStringIndex = 456;
const int kMenuActionStringIndex = 476;
const int kMenuSelfStringIndex = 497;
const int kMenuSayStringIndex = 502;
const int kMaxPatt = 20;
const int kResolutionScaler = 2;
/*
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, JULIA_ROOM = 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
};
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 MortevielleGameDescription *_gameDescription;
Common::Stack<int> _keypresses;
uint32 _lastGameFrame;
Common::Point _mousePos;
Common::StringArray _engineStrings;
Common::StringArray _gameStrings;
int _menuOpcode;
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 _juliaRoomPresenceLeo;
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 _curSearchObjId;
int _controlMenu;
int _startTime;
int _endTime;
Common::Point _stdPal[91][17];
int _x26KeyCount;
int _roomDoorId;
int _openObjCount;
int _takeObjCount;
int _num;
int _searchCount;
bool _introSpeechPlayed;
int _inGameHourDuration;
int _x;
int _y;
int _currentHourCount;
int _currentTime;
int _pauseStartTime;
Common::String _hintPctMessage;
byte *_cfiecBuffer;
int _cfiecBufferSize;
int _openObjects[7];
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 loadPalette();
void loadTexts();
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 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 setRandomPresenceJuliaRoom(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 displayTextBlock(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);
int getFirstObject();
void prepareNextObject();
void putObject();
void resetObjectPlace();
void resetCoreVar();
void drawDiscussionBox();
void displayNarrativePicture(int af, int ob);
void menuUp();
void displayLookScreen(int objId);
void adzon();
public:
Common::Point _prevPos;
int _currMenu;
int _currAction;
int _drawingSizeArr[108];
int _charAnswerCount[9];
int _charAnswerMax[9];
byte _tabdon[4001];
bool _soundOff;
bool _outsideOnlyFl;
bool _destinationOk;
bool _largestClearScreen;
float _addFix;
int _savedBitIndex;
int _numpal;
int _key;
bool _mouseClick;
SaveStruct _coreVar, _saveStruct;
int _maff;
int _caff;
int _crep;
int _is; // ???
byte _destinationArray[7][25];
byte *_curPict;
byte *_curAnim;
byte *_rightFramePict;
PaletteManager _paletteManager;
GfxSurface _backgroundSurface;
Common::RandomSource _randomSource;
ScreenSurface *_screenSurface;
SoundManager *_soundManager;
SavegameManager *_savegameManager;
Menu *_menu;
MouseHandler *_mouse;
TextHandler *_text;
DialogManager *_dialogManager;
MortevielleEngine(OSystem *system, const MortevielleGameDescription *gameDesc);
~MortevielleEngine() override;
bool hasFeature(EngineFeature f) const override;
bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override;
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override;
Common::Error loadGameState(int slot) override;
Common::Error saveGameState(int slot, const Common::String &desc, bool isAutosave = false) override;
Common::Error run() override;
void pauseEngineIntern(bool pause) override;
uint32 getGameFlags() const;
Common::Language getLanguage() const;
Common::Language getOriginalLanguage() const;
bool useOriginalData() const;
static Common::String generateSaveFilename(const Common::String &target, int slot);
Common::String getSaveStateName(int slot) const override {
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 displayInterScreenMessage(int mesgId);
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 clearScreen();
};
extern MortevielleEngine *g_vm;
} // End of namespace Mortevielle
#endif

View File

@@ -0,0 +1,268 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
/*
* 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 {
MouseHandler::MouseHandler(MortevielleEngine *vm) {
_vm = vm;
}
/**
* Initialize the mouse
* @remarks Originally called 'init_mouse'
*/
void MouseHandler::initMouse() {
_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 * kResolutionScaler)
newPos.x = 314 * kResolutionScaler;
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) {
int cx, cy;
bool click;
// Set defaults and check pending events
funct = false;
key = '\377';
bool 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;
char 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 * kResolutionScaler;
cy = 1;
break;
case '3':
cy = 190;
cx = 315 * kResolutionScaler;
break;
case '5':
cy = 100;
cx = 155 * kResolutionScaler;
break;
case ' ':
case '\15':
_vm->setMouseClick(true);
return;
break;
case '\33':
p_key = _vm->keyPressed();
if (p_key) {
char 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 = kResolutionScaler * 32;
cy = 8;
break;
case 'D':
cx = 80 * kResolutionScaler;
cy = 8;
break;
case 'A':
cx = 126 * kResolutionScaler;
cy = 8;
break;
case 'S':
cx = 174 * kResolutionScaler;
cy = 8;
break;
case 'P':
cx = 222 * kResolutionScaler;
cy = 8;
break;
case 'F':
cx = kResolutionScaler * 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;
}
} // End of namespace Mortevielle

View File

@@ -0,0 +1,52 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
/*
* 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;
public:
Common::Point _pos;
MouseHandler(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

View File

@@ -0,0 +1,331 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
/*
* 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 {
TextHandler::TextHandler(MortevielleEngine *vm) {
_vm = vm;
}
/**
* 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) {
Common::String s;
int i, j;
// Safeguard: add $ just in case
inputStr += '$';
_vm->_screenSurface->putxy(x, y);
int 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(const Common::Path &filename, const Common::Path &altFilename, int32 skipSize, int length) {
Common::File f;
if (!f.open(filename)) {
if (!f.open(altFilename))
error("Missing file: Either %s or %s", filename.toString().c_str(), altFilename.toString().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(const Common::Path &filename, int32 skipSize, int length) {
Common::File f;
if (!f.open(filename))
error("Missing file - %s", filename.toString().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 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 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::Path filename, altFilename;
if ((a != 50) && (a != 51)) {
int m = a + 2000;
if ((m > 2001) && (m < 2010))
m = 2001;
else if (m == 2011)
m = 2010;
if (a == 32)
m = 2034;
else if ((a == 17) && (_vm->_maff == 14))
m = 2018;
else if (a > 99) {
if ((_vm->_is == 1) || (_vm->_is == 0))
m = 2031;
else
m = 2032;
}
if ( ((a > 69) && (a < 80)) || (a == 30) || (a == 31) || (a == 144) || (a == 147) || (a == 149) )
m = 2030;
else if ( ((a < 27) && ( ((_vm->_maff > 69) && (!_vm->_coreVar._alreadyEnteredManor)) || (_vm->_maff > 99) )) || ((_vm->_maff > 29) && (_vm->_maff < 33)) )
m = 2033;
_vm->displayInterScreenMessage(m);
_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);
_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 != _vm->_menu->_opcodeEnter)) {
if ((a == ATTIC) || (a == CELLAR))
_vm->displayAloneText();
else if (!_vm->_outsideOnlyFl)
_vm->getPresence(_vm->_coreVar._currPlace);
_vm->_savedBitIndex = 0;
}
}
} // End of namespace Mortevielle

View File

@@ -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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
/*
* 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:
TextHandler (MortevielleEngine *vm);
void displayStr(Common::String inputStr, int x, int y, int dx, int dy, int typ);
void loadPictureFile(const Common::Path &filename, const Common::Path &altFilename, int32 skipSize, int length);
void loadAniFile(const Common::Path &filename, int32 skipSize, int length);
void taffich();
};
} // End of namespace Mortevielle
#endif

View File

@@ -0,0 +1,332 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
/*
* 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"
#include "graphics/paletteman.h"
namespace Mortevielle {
static const char SAVEGAME_ID[4] = { 'M', 'O', 'R', 'T' };
SavegameManager::SavegameManager(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'
*/
bool 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 == nullptr) {
if (!f.open(Common::Path(filename))) {
warning("Unable to open save file '%s'", filename.c_str());
return false;
}
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;
if (!readSavegameHeader(stream, header)) {
delete stream;
return false;
}
} else {
stream->seek(0);
}
// Read the game contents
Common::Serializer sz(stream, nullptr);
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;
return true;
}
/**
* Load a saved game
*/
Common::Error SavegameManager::loadGame(const Common::String &filename) {
g_vm->_mouse->hideMouse();
g_vm->displayEmptyHand();
if (loadSavegame(filename)) {
/* Initialization */
g_vm->charToHour();
g_vm->initGame();
g_vm->gameLoaded();
g_vm->_mouse->showMouse();
return Common::kNoError;
} else
return Common::kUnknownError;
}
/**
* 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->getSaveStateName(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(nullptr, 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->getSaveStateName(slot));
}
Common::Error SavegameManager::saveGame(int slot) {
return saveGame(slot, _vm->getSaveStateName(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.getPixels(), 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);
}
WARN_UNUSED_RESULT bool SavegameManager::readSavegameHeader(Common::InSaveFile *in, SavegameHeader &header, bool skipThumbnail) {
// 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
if (!Graphics::loadThumbnail(*in, header.thumbnail, skipThumbnail)) {
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 MetaEngine *metaEngine, const Common::String &target) {
Common::String pattern = target;
pattern += ".###";
Common::StringArray files = g_system->getSavefileManager()->listSavefiles(pattern);
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) {
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(metaEngine, slotNumber, saveDescription));
delete in;
}
}
Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
return saveList;
}
SaveStateDescriptor SavegameManager::querySaveMetaInfos(const MetaEngine *metaEngine, 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;
return SaveStateDescriptor(metaEngine, slot, Common::String::format("Savegame - %03d", slot));
} else {
// Get the savegame header information
SavegameHeader header;
if (!readSavegameHeader(f, header, false)) {
delete f;
return SaveStateDescriptor();
}
delete f;
// Create the return descriptor
SaveStateDescriptor desc(metaEngine, 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

View File

@@ -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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
/*
* 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/scaler.h"
#include "graphics/thumbnail.h"
#include "engines/savestate.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:
SavegameManager(MortevielleEngine *vm);
bool 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);
WARN_UNUSED_RESULT static bool readSavegameHeader(Common::InSaveFile *in, SavegameHeader &header, bool skipThumbnail = true);
static SaveStateList listSaves(const MetaEngine *metaEngine, const Common::String &target);
static SaveStateDescriptor querySaveMetaInfos(const MetaEngine *metaEngine, const Common::String &fileName);
};
} // End of namespace Mortevielle
#endif

View File

@@ -0,0 +1,853 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
/*
* 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 "mortevielle/dialogs.h"
#include "audio/audiostream.h"
#include "audio/decoders/raw.h"
#include "common/scummsys.h"
#include "common/config-manager.h"
#include "common/text-to-speech.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};
SoundManager::SoundManager(MortevielleEngine *vm, Audio::Mixer *mixer) {
_vm = vm;
_mixer = mixer;
_audioStream = nullptr;
_ambiantNoiseBuf = nullptr;
_noiseBuf = nullptr;
_ttsMan = g_system->getTextToSpeechManager();
if (_ttsMan) {
_ttsMan->setLanguage(ConfMan.get("language"));
_ttsMan->enable(ConfMan.getBool("tts_enabled"));
_ttsMan->stop();
_ttsMan->setRate(0);
_ttsMan->setPitch(0);
_ttsMan->setVolume(100);
}
_soundType = 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;
}
_buildingSentence = false;
_ptr_oct = 0;
_cfiphBuffer = nullptr;
}
SoundManager::~SoundManager() {
if (_audioStream)
_audioStream->finish();
free(_ambiantNoiseBuf);
free(_noiseBuf);
}
/**
* Decode music data
*/
int SoundManager::decodeMusic(const byte *PSrc, byte *PDest, int size) {
static const int tab[16] = { -96, -72, -48, -32, -20, -12, -8, -4, 0, 4, 8, 12, 20, 32, 48, 72 };
uint seed = 128;
int decompSize = 0;
int skipSize = 0;
for (int idx1 = 0; idx1 < size; ++idx1) {
byte srcByte = *PSrc++;
int v = tab[srcByte >> 4];
seed += v;
*PDest++ = seed & 0xff;
v = tab[srcByte & 0xf];
seed += v;
*PDest++ = seed & 0xff;
if (srcByte == 0)
skipSize += 2;
else {
decompSize += skipSize + 2;
skipSize = 0;
}
}
return decompSize;
}
/**
* Load sonmus.mor file
* @remarks Originally called 'charge_son'
*/
void SoundManager::loadAmbiantSounds() {
Common::File f;
if (!f.open("sonmus.mor"))
error("Missing file - sonmus.mor");
free(_ambiantNoiseBuf);
int size = f.size();
byte *compMusicBuf1 = (byte *)malloc(sizeof(byte) * size);
_ambiantNoiseBuf = (byte *)malloc(sizeof(byte) * size * 2);
f.read(compMusicBuf1, size);
f.close();
decodeMusic(compMusicBuf1, _ambiantNoiseBuf, size);
free(compMusicBuf1);
}
/**
* Speech function - Load Noise files
* @remarks Originally called 'charge_bruit' and 'charge_bruit5'
*/
void SoundManager::loadNoise() {
Common::File f1, f5;
if (!f5.open("bruit5"))
error("Missing file - bruit5");
if (f1.open("bruits")) { //Translation: "noise"
assert(f1.size() > 32000);
_noiseBuf = (byte *)malloc(sizeof(byte) * (f1.size() + f5.size()));
f1.read(_noiseBuf, 32000); // 250 * 128
f5.read(&_noiseBuf[32000], f5.size());
f1.read(&_noiseBuf[32000 + f5.size()], f1.size() - 32000); // 19072
f1.close();
} else {
Common::File f2, f3, f4;
if (!f1.open("bruit1") || !f2.open("bruit2") || !f3.open("bruit3") || !f4.open("bruit4"))
error("Missing file - bruits");
assert(f4.size() == 32000);
_noiseBuf = (byte *)malloc(sizeof(byte) * (f1.size() + f2.size() + f3.size() + f4.size() + f5.size()));
f4.read(_noiseBuf, f4.size());
int pos = f4.size();
f5.read(&_noiseBuf[pos], f5.size());
pos += f5.size();
f1.read(&_noiseBuf[pos], f1.size());
pos += f1.size();
f2.read(&_noiseBuf[pos], f2.size());
pos += f2.size();
f3.read(&_noiseBuf[pos], f3.size());
f1.close();
f2.close();
f3.close();
f4.close();
}
f5.close();
}
void SoundManager::regenbruit() {
int i = 69876;
for (int j = 0; j < 100; j++) {
_cfiphBuffer[j] = READ_BE_UINT16(&_noiseBuf[i]);
i += 2;
}
}
void SoundManager::litph(tablint &t, int typ, int tempo) {
if (!_buildingSentence) {
if (_mixer->isSoundHandleActive(_soundHandle))
_mixer->stopHandle(_soundHandle);
if (_ttsMan) {
if (_ttsMan->isSpeaking())
_ttsMan->stop();
}
_buildingSentence = true;
}
int freq = tempo * 252; // 25.2 * 10
int i = 0;
while (i < _ptr_oct) {
int idx = _troctBuf[i];
i++;
switch(idx) {
case 0: {
int val = _troctBuf[i];
i++;
if (_soundType == 1) {
debugC(5, kMortevielleSounds, "litph - duson");
const static int noiseAdr[] = {0, 17224,
17224, 33676,
33676, 51014,
51014, 59396,
59396, 61286,
61286, 69875};
if (val > 5) {
warning("unhandled index %d", val);
} else {
if (!_audioStream)
_audioStream = Audio::makeQueuingAudioStream(freq, false);
_audioStream->queueBuffer(&_noiseBuf[noiseAdr[val * 2]], noiseAdr[(val * 2) + 1] - noiseAdr[(val * 2)], DisposeAfterUse::NO, Audio::FLAG_UNSIGNED);
}
} else { // 2
debugC(5, kMortevielleSounds, "litph - vadson");
const static int ambiantNoiseAdr[] = {0, 14020,
14020, 18994,
18994, 19630,
19630, 22258,
22258, 37322,
37322, 44472,
44472, 52324,
52324, 59598,
59598, 69748};
if (val > 8) {
warning("unhandled index %d", val);
} else {
if (!_audioStream)
_audioStream = Audio::makeQueuingAudioStream(freq, false);
_audioStream->queueBuffer(&_ambiantNoiseBuf[ambiantNoiseAdr[val * 2]], ambiantNoiseAdr[(val * 2) + 1] - ambiantNoiseAdr[(val * 2)], DisposeAfterUse::NO, Audio::FLAG_UNSIGNED);
}
}
i++;
break;
}
case 2: {
int val = _troctBuf[i];
i++;
int tmpidx = (val * 12) + 268;
val = _troctBuf[i];
i++;
warning("TODO: reech %d %d", tmpidx, val);
}
break;
case 4:
if (_soundType) {
i += 2;
}
break;
case 6:
warning("TODO: pari2");
i += 2;
break;
default:
static byte emptyBuf[19] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
if (idx == 62)
warning("TODO: blab");
else if (idx == 32) {
if (!_audioStream)
_audioStream = Audio::makeQueuingAudioStream(freq, false);
_audioStream->queueBuffer(emptyBuf, 19, DisposeAfterUse::NO, Audio::FLAG_UNSIGNED);
} else if (idx == 35) {
if (i < _ptr_oct)
warning("unexpected 35 - stop the buffering");
i = _ptr_oct;
} else if (idx == 46) {
if (!_audioStream)
_audioStream = Audio::makeQueuingAudioStream(freq, false);
for (int j = 0; j < 10; j++)
_audioStream->queueBuffer(emptyBuf, 19, DisposeAfterUse::NO, Audio::FLAG_UNSIGNED);
} else {
warning("Other code: %d - %d %d", idx, _troctBuf[i], _troctBuf[i + 1]);
}
break;
}
}
}
void SoundManager::playSong(const byte* buf, uint size, uint loops) {
int freq = kTempoMusic * 252; // 25.2 * 10
Audio::SeekableAudioStream *raw = Audio::makeRawStream(buf, size, freq, Audio::FLAG_UNSIGNED, DisposeAfterUse::NO);
Audio::AudioStream *stream = Audio::makeLoopingAudioStream(raw, loops);
Audio::SoundHandle songHandle;
_mixer->playStream(Audio::Mixer::kSFXSoundType, &songHandle, stream, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::YES);
while (_mixer->isSoundHandleActive(songHandle) && !_vm->keyPressed() && !_vm->_mouseClick && !_vm->shouldQuit())
;
// In case the handle is still active, stop it.
_mixer->stopHandle(songHandle);
}
void SoundManager::spfrac(int wor) {
_queue[2]._rep = (uint)wor >> 12;
_queue[2]._freq = ((uint)wor >> 6) & 7;
_queue[2]._acc = ((uint)wor >> 9) & 7;
}
void SoundManager::charg_car(int &currWordNumb) {
assert(currWordNumb < 1712);
int wor = READ_BE_UINT16(&_wordBuf[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_ -= 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 SoundManager::entroct(byte o) {
assert(_ptr_oct < 10576);
_troctBuf[_ptr_oct] = o;
++_ptr_oct;
}
void SoundManager::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);
}
}
/**
* Load phoneme sound file
* @remarks Originally called 'charge_phbruit'
*/
void SoundManager::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();
}
void SoundManager::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) {
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) {
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);
}
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:
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)) {
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:
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)) {
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 SoundManager::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 SoundManager::initQueue() {
_queue[2]._rep = 0;
_queue[2]._freq = 0;
_queue[2]._acc = 0;
moveQueue();
moveQueue();
}
/**
* Handle a phoneme
* @remarks Originally called 'trait_ph'
*/
void SoundManager::handlePhoneme() {
const uint16 deca[3] = {300, 30, 40};
uint16 startPos = _cfiphBuffer[_phonemeNumb - 1] + deca[_soundType];
uint16 endPos = _cfiphBuffer[_phonemeNumb] + deca[_soundType];
int wordCount = endPos - startPos;
startPos /= 2;
endPos /= 2;
assert((endPos - startPos) < 1711);
for (int i = startPos, currWord = 0; i < endPos; i++, currWord += 2)
WRITE_BE_UINT16(&_wordBuf[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)'#');
#ifdef DEBUG
warning("---");
for (int i = 0; i < _ptr_oct; ) {
if ((_troctBuf[i] == 32) || (_troctBuf[i] == 35) || (_troctBuf[i] == 46)) {
warning("%d", _troctBuf[i]);
i++;
} else {
warning("%d %d %d", _troctBuf[i], _troctBuf[i + 1], _troctBuf[i + 1]);
i += 3;
}
}
warning("---");
#endif
}
/**
* Start speech
* @remarks Originally called 'parole'
*/
void SoundManager::startSpeech(int rep, int character, int typ) {
if (_vm->_soundOff)
return;
_soundType = typ;
if (typ == 0) {
// Speech
const int haut[9] = { 0, 0, 1, -3, 6, -2, 2, 7, -1 };
const int voiceIndices[9] = { 0, 1, 2, 3, 0, 4, 5, 1, 6 };
if (!_ttsMan || !ConfMan.getBool("tts_enabled"))
return;
Common::Array<int> voices;
int pitch = haut[character];
bool male;
if (haut[character] > 5) {
voices = _ttsMan->getVoiceIndicesByGender(Common::TTSVoice::FEMALE);
male = false;
pitch -= 6;
} else {
voices = _ttsMan->getVoiceIndicesByGender(Common::TTSVoice::MALE);
male = true;
}
pitch *= 5;
// If there is no voice available for the given gender, just set it to the 0th
// voice
if (voices.empty())
_ttsMan->setVoice(0);
else {
int voiceIndex = voiceIndices[character] % voices.size();
_ttsMan->setVoice(voices[voiceIndex]);
}
// If the selected voice is a different gender, than we want, just try to
// set the pitch so it may sound a little bit closer to the gender we want
if (!((_ttsMan->getVoice().getGender() == Common::TTSVoice::MALE) == male)) {
if (male)
pitch -= 50;
else
pitch += 50;
}
_ttsMan->setPitch(pitch);
_ttsMan->say(_vm->getString(rep + kDialogStringIndex), Common::kDos850);
return;
}
uint16 savph[501];
int tempo;
_phonemeNumb = rep;
for (int i = 0; i <= 500; ++i)
savph[i] = _cfiphBuffer[i];
tempo = kTempoNoise;
_vm->_addFix = (float)((tempo - 8)) / 256;
cctable(_tbi);
switch (typ) {
case 1:
regenbruit();
break;
case 2:
loadPhonemeSounds();
break;
default:
break;
}
handlePhoneme();
litph(_tbi, typ, tempo);
_buildingSentence = false;
_audioStream->finish();
_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundHandle, _audioStream);
_audioStream = nullptr;
for (int i = 0; i <= 500; ++i)
_cfiphBuffer[i] = savph[i];
_vm->setPal(_vm->_numpal);
}
void SoundManager::waitSpeech() {
if (_soundType == 0) {
if (!_ttsMan)
return;
while (_ttsMan->isSpeaking() && !_vm->keyPressed() && !_vm->_mouseClick && !_vm->shouldQuit())
;
// In case the TTS is still speaking, stop it.
_ttsMan->stop();
} else {
while (_mixer->isSoundHandleActive(_soundHandle) && !_vm->keyPressed() && !_vm->_mouseClick && !_vm->shouldQuit())
;
// In case the handle is still active, stop it.
_mixer->stopHandle(_soundHandle);
}
if (!_vm->keyPressed() && !_vm->_mouseClick && !_vm->shouldQuit())
g_system->delayMillis(600);
}
} // End of namespace Mortevielle

108
engines/mortevielle/sound.h Normal file
View File

@@ -0,0 +1,108 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
/*
* 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/mixer.h"
#include "common/mutex.h"
#include "common/queue.h"
#include "common/text-to-speech.h"
namespace Audio {
class QueuingAudioStream;
}
namespace Mortevielle {
class MortevielleEngine;
const int kNullValue = 255;
const int kTempoMusic = 71;
const int kTempoNoise = 78;
const int kTempoF = 80;
const int kTempoM = 89;
struct SpeechQueue {
int _val;
int _code;
int _acc;
int _freq;
int _rep;
};
typedef int tablint[256];
class SoundManager {
private:
MortevielleEngine *_vm;
byte *_ambiantNoiseBuf;
byte *_noiseBuf;
int _phonemeNumb;
int _soundType;
SpeechQueue _queue[3];
byte _wordBuf[1712];
byte _troctBuf[10576];
bool _buildingSentence;
int _ptr_oct;
int _tbi[256];
Audio::QueuingAudioStream *_audioStream;
void loadPhonemeSounds();
void moveQueue();
void initQueue();
void handlePhoneme();
void spfrac(int wor);
void charg_car(int &currWordNumb);
void entroct(byte o);
void cctable(tablint &t);
void trait_car();
void regenbruit();
void litph(tablint &t, int typ, int tempo);
public:
SoundManager(MortevielleEngine *vm, Audio::Mixer *mixer);
~SoundManager();
Audio::Mixer *_mixer;
Common::TextToSpeechManager *_ttsMan;
Audio::SoundHandle _soundHandle;
uint16 *_cfiphBuffer;
int decodeMusic(const byte *PSrc, byte *PDest, int size);
void playSong(const byte *buf, uint usize, uint loops);
void loadAmbiantSounds();
void loadNoise();
void startSpeech(int rep, int character, int typ);
void waitSpeech();
};
} // End of namespace Mortevielle
#endif

File diff suppressed because it is too large Load Diff