1527 lines
42 KiB
C++
1527 lines
42 KiB
C++
/* 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 "common/scummsys.h"
|
||
#include "common/config-manager.h"
|
||
#include "common/debug-channels.h"
|
||
#include "common/debug.h"
|
||
#include "common/events.h"
|
||
#include "common/file.h"
|
||
#include "common/random.h"
|
||
#include "common/fs.h"
|
||
#include "common/keyboard.h"
|
||
#include "common/substream.h"
|
||
#include "common/str.h"
|
||
|
||
#include "graphics/surface.h"
|
||
#include "graphics/pixelformat.h"
|
||
|
||
#include "engines/util.h"
|
||
|
||
#include "prince/prince.h"
|
||
#include "prince/graphics.h"
|
||
#include "prince/script.h"
|
||
#include "prince/debugger.h"
|
||
#include "prince/object.h"
|
||
#include "prince/mob.h"
|
||
#include "prince/music.h"
|
||
#include "prince/variatxt.h"
|
||
#include "prince/font.h"
|
||
#include "prince/mhwanh.h"
|
||
#include "prince/cursor.h"
|
||
#include "prince/archive.h"
|
||
#include "prince/hero.h"
|
||
#include "prince/animation.h"
|
||
#include "prince/curve_values.h"
|
||
|
||
namespace Prince {
|
||
|
||
#ifdef USE_TTS
|
||
|
||
// Mazovia encoding
|
||
static const uint16 polishEncodingTable[] = {
|
||
0x86, 0xc485, 0x8d, 0xc487, 0x8f, 0xc484, 0x90, 0xc498, // ą, ć, Ą, Ę
|
||
0x91, 0xc499, 0x92, 0xc582, 0x95, 0xc486, 0x98, 0xc59a, // ę, ł, Ć, Ś
|
||
0x9c, 0xc581, 0x9e, 0xc59b, 0xa0, 0xc5b9, 0xa1, 0xc5bb, // Ł, ś, Ź, Ż
|
||
0xa2, 0xc3b3, 0xa3, 0xc393, 0xa4, 0xc584, 0xa5, 0xc583, // ó, Ó, ń, Ń
|
||
0xa6, 0xc5ba, 0xa7, 0xc5bc, // ź, ż
|
||
0
|
||
};
|
||
|
||
// Custom encoding
|
||
static const uint16 russianEncodingTable[] = {
|
||
0x46, 0xd090, 0x66, 0xd0b0, 0x83, 0xd091, 0x92, 0xd0b1, // А, а, Б, б
|
||
0x44, 0xd092, 0x64, 0xd0b2, 0x55, 0xd093, 0x75, 0xd0b3, // В, в, Г, г
|
||
0x4c, 0xd094, 0x6c, 0xd0b4, 0x54, 0xd095, 0x74, 0xd0b5, // Д, д, Е, е
|
||
0x81, 0xd096, 0x8f, 0xd0b6, 0x50, 0xd097, 0x70, 0xd0b7, // Ж, ж, З, з
|
||
0x42, 0xd098, 0x62, 0xd0b8, 0x51, 0xd099, 0x71, 0xd0b9, // И, и, Й, й
|
||
0x52, 0xd09a, 0x72, 0xd0ba, 0x4b, 0xd09b, 0x6b, 0xd0bb, // К, к, Л, л
|
||
0x56, 0xd09c, 0x76, 0xd0bc, 0x59, 0xd09d, 0x79, 0xd0bd, // М, м, Н, н
|
||
0x4a, 0xd09e, 0x6a, 0xd0be, 0x47, 0xd09f, 0x67, 0xd0bf, // О, о, П, п
|
||
0x48, 0xd0a0, 0x68, 0xd180, 0x43, 0xd0a1, 0x63, 0xd181, // Р, р, С, с
|
||
0x4e, 0xd0a2, 0x6e, 0xd182, 0x45, 0xd0a3, 0x65, 0xd183, // Т, т, У, у
|
||
0x41, 0xd0a4, 0x61, 0xd184, 0x7f, 0xd0a5, 0x85, 0xd185, // Ф, ф, Х, х
|
||
0x57, 0xd0a6, 0x77, 0xd186, 0x58, 0xd0a7, 0x78, 0xd187, // Ц, ц, Ч, ч
|
||
0x49, 0xd0a8, 0x69, 0xd188, 0x4f, 0xd0a9, 0x6f, 0xd189, // Ш, ш, Щ, щ
|
||
0x8d, 0xd18a, 0x53, 0xd0ab, 0x73, 0xd18b, 0x4d, 0xd0ac, // ъ, Ы, ы, Ь
|
||
0x6d, 0xd18c, 0x82, 0xd0ad, 0x90, 0xd18d, 0x92, 0xd18e, // ь, Э, э, ю
|
||
0x5a, 0xd0af, 0x7a, 0xd18f, // Я, я
|
||
0
|
||
};
|
||
|
||
// Custom encoding
|
||
static const uint16 spanishEncodingTable[] = {
|
||
0x25, 0xc3ad, 0x26, 0xc3ba, 0x35, 0xc3a1, 0x36, 0xc3a9, // í, ú, á, é
|
||
0x37, 0xc2bf, 0x38, 0xc3b1, 0x3b, 0xc2a1, 0x5f, 0xc3b3, // ¿, ñ, ¡, ó
|
||
0
|
||
};
|
||
|
||
// Custom encoding
|
||
static const uint16 germanEncodingTable[] = {
|
||
0x83, 0xc384, 0x84, 0xc396, 0x85, 0xc39c, 0x7f, 0xc39f, // Ä, Ö, Ü, ß
|
||
0x80, 0xc3a4, 0x81, 0xc3b6, 0x82, 0xc3bc, // ä, ö, ü
|
||
0
|
||
};
|
||
|
||
struct CharacterVoiceData {
|
||
uint8 textColor;
|
||
uint8 voiceID;
|
||
uint8 locationNumber;
|
||
int8 mobIndex;
|
||
bool male;
|
||
};
|
||
|
||
static const CharacterVoiceData characterVoiceData[] = {
|
||
{ 220, 0, 0, -1, true }, // Hero
|
||
{ 216, 0, 0, -1, true }, // Hover text
|
||
{ 211, 1, 7, -1, true }, // Bard
|
||
{ 211, 1, 5, 11, true }, // Bard (tavern)
|
||
{ 211, 2, 6, -1, true }, // Witch
|
||
{ 211, 3, 0, -1, true }, // Arivald (all other cases of text color 211)
|
||
{ 202, 4, 1, -1, true }, // Grave digger
|
||
{ 202, 0, 13, -1, false }, // Sheila
|
||
{ 253, 5, 4, -1, true }, // Tall merchant
|
||
{ 253, 6, 54, -1, true }, // Butcher
|
||
{ 225, 7, 4, -1, true }, // Thief
|
||
{ 225, 8, 6, -1, true }, // Alchemist
|
||
{ 225, 1, 7, -1, false }, // Bard's wife
|
||
{ 236, 9, 4, 19, true }, // Fat merchant
|
||
{ 236, 9, 4, 2, true }, // Fat merchant (initial town cutscene)
|
||
{ 236, 10, 25, 4, true }, // Dragon
|
||
{ 236, 2, 0, -1, false }, // Shandria (all other cases of text color 236)
|
||
{ 246, 11, 5, -1, true }, // Monk
|
||
{ 246, 12, 31, -1, true }, // Priest
|
||
{ 195, 13, 0, -1, true }, // Zandahan
|
||
{ 195, 14, 3, -1, true }, // Hermit
|
||
{ 252, 15, 10, -1, true }, // Gate guard
|
||
{ 252, 16, 12, -1, true }, // Courtyard guard
|
||
{ 252, 17, 30, -1, true }, // Passerby
|
||
{ 196, 18, 30, -1, true }, // Modern merchant
|
||
{ 196, 3, 5, -1, false }, // Stranger
|
||
{ 244, 19, 43, -1, true }, // Devil
|
||
{ 244, 20, 0, -1, true }, // Devil
|
||
{ 203, 4, 0, -1, false }, // Witch
|
||
{ 197, 21, 0, -1, true }, // Short merchant
|
||
{ 212, 22, 0, -1, true }, // Merchant cooking soup
|
||
{ 200, 23, 0, -1, true }, // Homunculus
|
||
{ 205, 24, 0, -1, true }, // Beggar
|
||
{ 232, 25, 0, -1, true }, // Dwarf
|
||
{ 208, 26, 0, -1, true }, // Barkeeper
|
||
{ 235, 27, 42, -1, true }, // Devil
|
||
{ 235, 28, 0, -1, true }, // Lord Sun
|
||
{ 201, 5, 0, -1, false }, // Elegant lady
|
||
{ 245, 29, 0, -1, true }, // Devil
|
||
{ 219, 30, 0, -1, true }, // Lucifer
|
||
{ 217, 31, 0, -1, true } // Narrator
|
||
};
|
||
|
||
static const int kCharacterVoiceDataCount = ARRAYSIZE(characterVoiceData);
|
||
|
||
#endif
|
||
|
||
static const uint8 kNarratorTextColor = 217;
|
||
|
||
void PrinceEngine::debugEngine(const char *s, ...) {
|
||
char buf[STRINGBUFLEN];
|
||
va_list va;
|
||
|
||
va_start(va, s);
|
||
vsnprintf(buf, STRINGBUFLEN, s, va);
|
||
va_end(va);
|
||
|
||
debug("Prince::Engine %s", buf);
|
||
}
|
||
|
||
PrinceEngine::PrinceEngine(OSystem *syst, const PrinceGameDescription *gameDesc) :
|
||
Engine(syst), _gameDescription(gameDesc), _graph(nullptr), _script(nullptr), _interpreter(nullptr), _flags(nullptr),
|
||
_locationNr(0), _debugger(nullptr), _midiPlayer(nullptr), _room(nullptr),
|
||
_cursor1(nullptr), _cursor2(nullptr), _cursor3(nullptr), _font(nullptr),
|
||
_suitcaseBmp(nullptr), _roomBmp(nullptr), _cursorNr(0), _picWindowX(0), _picWindowY(0), _randomSource("prince"),
|
||
_invLineX(134), _invLineY(176), _invLine(5), _invLines(3), _invLineW(70), _invLineH(76), _maxInvW(72), _maxInvH(76),
|
||
_printMapNotification(false), _invLineSkipX(2), _invLineSkipY(3), _showInventoryFlag(false), _inventoryBackgroundRemember(false),
|
||
_mst_shadow(0), _mst_shadow2(0), _candleCounter(0), _invX1(53), _invY1(18), _invWidth(536), _invHeight(438),
|
||
_invCurInside(false), _optionsFlag(false), _optionEnabled(0), _invExamY(120), _invMaxCount(2), _invCounter(0),
|
||
_optionsMob(-1), _currentPointerNumber(1), _selectedMob(-1), _previousMob(-1), _dialogMob(-1), _selectedItem(0), _selectedMode(0),
|
||
_optionsWidth(210), _optionsHeight(170), _invOptionsWidth(210), _invOptionsHeight(130), _optionsStep(20),
|
||
_invOptionsStep(20), _optionsNumber(7), _invOptionsNumber(5), _optionsColor1(236), _optionsColor2(252),
|
||
_dialogWidth(600), _dialogHeight(0), _dialogLineSpace(10), _dialogColor1(220), _dialogColor2(223),
|
||
_dialogFlag(false), _dialogLines(0), _dialogText(nullptr), _previousSelectedDialog(-1), _isConversing(false), _mouseFlag(1),
|
||
_roomPathBitmap(nullptr), _roomPathBitmapTemp(nullptr), _coordsBufEnd(nullptr), _coordsBuf(nullptr), _coords(nullptr),
|
||
_traceLineLen(0), _rembBitmapTemp(nullptr), _rembBitmap(nullptr), _rembMask(0), _rembX(0), _rembY(0), _fpX(0), _fpY(0),
|
||
_checkBitmapTemp(nullptr), _checkBitmap(nullptr), _checkMask(0), _checkX(0), _checkY(0), _traceLineFirstPointFlag(false),
|
||
_tracePointFirstPointFlag(false), _coordsBuf2(nullptr), _coords2(nullptr), _coordsBuf3(nullptr), _coords3(nullptr),
|
||
_shanLen(0), _directionTable(nullptr), _currentMidi(0), _lightX(0), _lightY(0), _curveData(nullptr), _curvPos(0),
|
||
_creditsData(nullptr), _creditsDataSize(0), _currentTime(0), _zoomBitmap(nullptr), _shadowBitmap(nullptr), _transTable(nullptr),
|
||
_flcFrameSurface(nullptr), _shadScaleValue(0), _shadLineLen(0), _scaleValue(0), _dialogImage(nullptr), _mobTranslationData(nullptr),
|
||
_mobTranslationSize(0), _missingVoice(false), _intro(false), _credits(false) {
|
||
|
||
DebugMan.enableDebugChannel("script");
|
||
|
||
memset(_audioStream, 0, sizeof(_audioStream));
|
||
}
|
||
|
||
PrinceEngine::~PrinceEngine() {
|
||
delete _rnd;
|
||
delete _cursor1;
|
||
delete _cursor3;
|
||
delete _midiPlayer;
|
||
delete _script;
|
||
delete _flags;
|
||
delete _interpreter;
|
||
delete _font;
|
||
delete _roomBmp;
|
||
delete _suitcaseBmp;
|
||
delete _variaTxt;
|
||
free(_talkTxt);
|
||
free(_invTxt);
|
||
free(_dialogDat);
|
||
delete _graph;
|
||
delete _room;
|
||
//_debugger is deleted by Engine
|
||
|
||
if (_cursor2 != nullptr) {
|
||
_cursor2->free();
|
||
delete _cursor2;
|
||
}
|
||
|
||
for (uint i = 0; i < _objList.size(); i++) {
|
||
delete _objList[i];
|
||
}
|
||
_objList.clear();
|
||
|
||
free(_objSlot);
|
||
|
||
for (uint32 i = 0; i < _pscrList.size(); i++) {
|
||
delete _pscrList[i];
|
||
}
|
||
_pscrList.clear();
|
||
|
||
for (uint i = 0; i < _maskList.size(); i++) {
|
||
free(_maskList[i]._data);
|
||
}
|
||
_maskList.clear();
|
||
|
||
_drawNodeList.clear();
|
||
|
||
clearBackAnimList();
|
||
_backAnimList.clear();
|
||
|
||
freeAllNormAnims();
|
||
_normAnimList.clear();
|
||
|
||
for (uint i = 0; i < _allInvList.size(); i++) {
|
||
_allInvList[i]._surface->free();
|
||
delete _allInvList[i]._surface;
|
||
}
|
||
_allInvList.clear();
|
||
|
||
_optionsPic->free();
|
||
delete _optionsPic;
|
||
|
||
_optionsPicInInventory->free();
|
||
delete _optionsPicInInventory;
|
||
|
||
for (uint i = 0; i < _mainHero->_moveSet.size(); i++) {
|
||
delete _mainHero->_moveSet[i];
|
||
}
|
||
|
||
for (uint i = 0; i < _secondHero->_moveSet.size(); i++) {
|
||
delete _secondHero->_moveSet[i];
|
||
}
|
||
|
||
delete _mainHero;
|
||
delete _secondHero;
|
||
|
||
free(_roomPathBitmap);
|
||
free(_roomPathBitmapTemp);
|
||
free(_coordsBuf);
|
||
|
||
_mobPriorityList.clear();
|
||
|
||
freeAllSamples();
|
||
|
||
free(_zoomBitmap);
|
||
free(_shadowBitmap);
|
||
free(_transTable);
|
||
|
||
free(_curveData);
|
||
|
||
free(_shadowLine);
|
||
|
||
free(_creditsData);
|
||
|
||
if (_dialogImage != nullptr) {
|
||
_dialogImage->free();
|
||
delete _dialogImage;
|
||
}
|
||
|
||
free(_mobTranslationData);
|
||
}
|
||
|
||
void PrinceEngine::init() {
|
||
|
||
const Common::FSNode gameDataDir(ConfMan.getPath("path"));
|
||
|
||
debugEngine("Adding all path: %s", gameDataDir.getPath().toString(Common::Path::kNativeSeparator).c_str());
|
||
|
||
if (!(getFeatures() & GF_EXTRACTED)) {
|
||
PtcArchive *all = new PtcArchive();
|
||
if (!all->open("all/databank.ptc"))
|
||
error("Can't open all/databank.ptc");
|
||
|
||
PtcArchive *voices = new PtcArchive();
|
||
|
||
if (!(getFeatures() & GF_NOVOICES)) {
|
||
if (!voices->open("voices/databank.ptc"))
|
||
error("Can't open voices/databank.ptc");
|
||
}
|
||
|
||
PtcArchive *sound = new PtcArchive();
|
||
if (!sound->open("sound/databank.ptc"))
|
||
error("Can't open sound/databank.ptc");
|
||
|
||
SearchMan.addSubDirectoryMatching(gameDataDir, "all");
|
||
|
||
// Prefix the archive names, so that "all" doesn't conflict with the
|
||
// "all" directory, if that happens to be named in all lower case.
|
||
// It isn't on the CD, but we should try to stay case-insensitive.
|
||
SearchMan.add("_all", all);
|
||
SearchMan.add("_voices", voices);
|
||
SearchMan.add("_sound", sound);
|
||
} else {
|
||
SearchMan.addSubDirectoryMatching(gameDataDir, "all");
|
||
SearchMan.addSubDirectoryMatching(gameDataDir, "voices");
|
||
SearchMan.addSubDirectoryMatching(gameDataDir, "sound");
|
||
}
|
||
|
||
if (getFeatures() & GF_TRANSLATED) {
|
||
PtcArchive *translation = new PtcArchive();
|
||
if (getFeatures() & GF_TRANSLATED) {
|
||
if (!translation->openTranslation("prince_translation.dat"))
|
||
error("Can't open prince_translation.dat");
|
||
}
|
||
|
||
SearchMan.add("translation", translation);
|
||
}
|
||
|
||
_graph = new GraphicsMan(this);
|
||
|
||
_rnd = new Common::RandomSource("prince");
|
||
|
||
_midiPlayer = new MusicPlayer(this);
|
||
|
||
if (getLanguage() == Common::DE_DEU) {
|
||
_font = new Font();
|
||
Resource::loadResource(_font, "font3.raw", true);
|
||
} else {
|
||
_font = new Font();
|
||
Resource::loadResource(_font, "font1.raw", true);
|
||
}
|
||
|
||
_suitcaseBmp = new MhwanhDecoder();
|
||
Resource::loadResource(_suitcaseBmp, "walizka", true);
|
||
|
||
_script = new Script(this);
|
||
Resource::loadResource(_script, "skrypt.dat", true);
|
||
|
||
_flags = new InterpreterFlags();
|
||
_interpreter = new Interpreter(this, _script, _flags);
|
||
|
||
_debugger = new Debugger(this, _flags);
|
||
setDebugger(_debugger);
|
||
|
||
_variaTxt = new VariaTxt();
|
||
if (getFeatures() & GF_TRANSLATED) {
|
||
Resource::loadResource(_variaTxt, "variatxt_translate.dat", true);
|
||
} else {
|
||
Resource::loadResource(_variaTxt, "variatxt.dat", true);
|
||
}
|
||
|
||
_cursor1 = new Cursor();
|
||
Resource::loadResource(_cursor1, "mouse1.cur", true);
|
||
|
||
_cursor3 = new Cursor();
|
||
Resource::loadResource(_cursor3, "mouse2.cur", true);
|
||
|
||
Common::SeekableReadStream *talkTxtStream;
|
||
if (getFeatures() & GF_TRANSLATED) {
|
||
talkTxtStream = SearchMan.createReadStreamForMember("talktxt_translate.dat");
|
||
} else {
|
||
talkTxtStream = SearchMan.createReadStreamForMember("talktxt.dat");
|
||
}
|
||
if (!talkTxtStream) {
|
||
error("Can't load talkTxtStream");
|
||
return;
|
||
}
|
||
_talkTxtSize = talkTxtStream->size();
|
||
_talkTxt = (byte *)malloc(_talkTxtSize);
|
||
talkTxtStream->read(_talkTxt, _talkTxtSize);
|
||
|
||
delete talkTxtStream;
|
||
|
||
Common::SeekableReadStream *invTxtStream;
|
||
if (getFeatures() & GF_TRANSLATED) {
|
||
invTxtStream = SearchMan.createReadStreamForMember("invtxt_translate.dat");
|
||
} else {
|
||
invTxtStream = SearchMan.createReadStreamForMember("invtxt.dat");
|
||
}
|
||
if (!invTxtStream) {
|
||
error("Can't load invTxtStream");
|
||
return;
|
||
}
|
||
_invTxtSize = invTxtStream->size();
|
||
_invTxt = (byte *)malloc(_invTxtSize);
|
||
invTxtStream->read(_invTxt, _invTxtSize);
|
||
|
||
delete invTxtStream;
|
||
|
||
loadAllInv();
|
||
|
||
Common::SeekableReadStream *dialogDatStream = SearchMan.createReadStreamForMember("dialog.dat");
|
||
if (!dialogDatStream) {
|
||
error("Can't load dialogDatStream");
|
||
return;
|
||
}
|
||
|
||
dialogDatStream = Resource::getDecompressedStream(dialogDatStream);
|
||
|
||
_dialogDatSize = dialogDatStream->size();
|
||
_dialogDat = (byte *)malloc(_dialogDatSize);
|
||
dialogDatStream->read(_dialogDat, _dialogDatSize);
|
||
|
||
delete dialogDatStream;
|
||
|
||
_optionsPic = new Graphics::Surface();
|
||
_optionsPic->create(_optionsWidth, _optionsHeight, Graphics::PixelFormat::createFormatCLUT8());
|
||
Common::Rect picRect(0, 0, _optionsWidth, _optionsHeight);
|
||
_optionsPic->fillRect(picRect, _graph->kShadowColor);
|
||
|
||
_optionsPicInInventory = new Graphics::Surface();
|
||
_optionsPicInInventory->create(_invOptionsWidth, _invOptionsHeight, Graphics::PixelFormat::createFormatCLUT8());
|
||
Common::Rect invPicRect(0, 0, _invOptionsWidth, _invOptionsHeight);
|
||
_optionsPicInInventory->fillRect(invPicRect, _graph->kShadowColor);
|
||
|
||
_roomBmp = new Image::BitmapDecoder();
|
||
|
||
_room = new Room();
|
||
|
||
_mainHero = new Hero(this, _graph);
|
||
_secondHero = new Hero(this, _graph);
|
||
_secondHero->_maxBoredom = 140;
|
||
_secondHero->loadAnimSet(3);
|
||
|
||
_roomPathBitmap = (byte *)malloc(kPathBitmapLen);
|
||
_roomPathBitmapTemp = (byte *)malloc(kPathBitmapLen);
|
||
_coordsBuf = (byte *)malloc(kTracePts * 4);
|
||
_coords = _coordsBuf;
|
||
_coordsBufEnd = _coordsBuf + kTracePts * 4 - 4;
|
||
|
||
BackgroundAnim tempBackAnim;
|
||
tempBackAnim._seq._currRelative = 0;
|
||
for (int i = 0; i < kMaxBackAnims; i++) {
|
||
_backAnimList.push_back(tempBackAnim);
|
||
}
|
||
|
||
Anim tempAnim;
|
||
tempAnim._animData = nullptr;
|
||
tempAnim._shadowData = nullptr;
|
||
for (int i = 0; i < kMaxNormAnims; i++) {
|
||
_normAnimList.push_back(tempAnim);
|
||
}
|
||
|
||
_objSlot = (uint16 *)malloc(kMaxObjects * sizeof(uint16));
|
||
for (int i = 0; i < kMaxObjects; i++) {
|
||
_objSlot[i] = 0xFF;
|
||
}
|
||
|
||
_zoomBitmap = (byte *)malloc(kZoomBitmapLen);
|
||
_shadowBitmap = (byte *)malloc(2 * kShadowBitmapSize);
|
||
_transTable = (byte *)malloc(kTransTableSize);
|
||
|
||
_curveData = (int16 *)malloc(2 * kCurveLen * sizeof(int16));
|
||
|
||
_shadowLine = (byte *)malloc(kShadowLineArraySize);
|
||
|
||
Common::SeekableReadStream *creditsDataStream;
|
||
if (getFeatures() & GF_TRANSLATED) {
|
||
creditsDataStream = SearchMan.createReadStreamForMember("credits_translate.dat");
|
||
} else {
|
||
creditsDataStream = SearchMan.createReadStreamForMember("credits.dat");
|
||
}
|
||
if (!creditsDataStream) {
|
||
error("Can't load creditsDataStream");
|
||
return;
|
||
}
|
||
_creditsDataSize = creditsDataStream->size();
|
||
_creditsData = (byte *)malloc(_creditsDataSize);
|
||
creditsDataStream->read(_creditsData, _creditsDataSize);
|
||
delete creditsDataStream;
|
||
|
||
if (getFeatures() & GF_TRANSLATED) {
|
||
loadMobTranslationTexts();
|
||
}
|
||
|
||
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
|
||
if (ttsMan != nullptr) {
|
||
ttsMan->enable(ConfMan.getBool("tts_enabled_objects") || ConfMan.getBool("tts_enabled_speech") || ConfMan.getBool("tts_enabled_missing_voice"));
|
||
ttsMan->setLanguage(ConfMan.get("language"));
|
||
}
|
||
}
|
||
|
||
void PrinceEngine::showLogo() {
|
||
MhwanhDecoder logo;
|
||
if (Resource::loadResource(&logo, "logo.raw", true)) {
|
||
loadSample(0, "LOGO.WAV");
|
||
playSample(0, 0);
|
||
_graph->draw(_graph->_frontScreen, logo.getSurface());
|
||
_graph->change();
|
||
_graph->update(_graph->_frontScreen);
|
||
setPalette(logo.getPalette().data());
|
||
|
||
uint32 logoStart = _system->getMillis();
|
||
while (_system->getMillis() < logoStart + 5000) {
|
||
Common::Event event;
|
||
Common::EventManager *eventMan = _system->getEventManager();
|
||
while (eventMan->pollEvent(event)) {
|
||
switch (event.type) {
|
||
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
|
||
if (event.customType == kActionSkip) {
|
||
stopSample(0);
|
||
return;
|
||
}
|
||
break;
|
||
case Common::EVENT_LBUTTONDOWN:
|
||
stopSample(0);
|
||
return;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (shouldQuit()) {
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
Common::Error PrinceEngine::run() {
|
||
syncSoundSettings();
|
||
int startGameSlot = ConfMan.hasKey("save_slot") ? ConfMan.getInt("save_slot") : -1;
|
||
init();
|
||
if (startGameSlot == -1) {
|
||
_intro = true;
|
||
playVideo("topware.avi");
|
||
showLogo();
|
||
} else {
|
||
loadLocation(59); // load intro location - easiest way to set everything up
|
||
loadGame(startGameSlot);
|
||
}
|
||
mainLoop();
|
||
return Common::kNoError;
|
||
}
|
||
|
||
void PrinceEngine::pauseEngineIntern(bool pause) {
|
||
Engine::pauseEngineIntern(pause);
|
||
|
||
if (_midiPlayer) {
|
||
if (pause) {
|
||
_midiPlayer->pause();
|
||
} else {
|
||
_midiPlayer->resume();
|
||
}
|
||
}
|
||
}
|
||
|
||
void PrinceEngine::setShadowScale(int32 shadowScale) {
|
||
shadowScale = 100 - shadowScale;
|
||
if (!shadowScale) {
|
||
_shadScaleValue = 10000;
|
||
} else {
|
||
_shadScaleValue = 10000 / shadowScale;
|
||
}
|
||
}
|
||
|
||
bool PrinceEngine::playNextFLCFrame() {
|
||
if (!_flicPlayer.isVideoLoaded())
|
||
return false;
|
||
|
||
const Graphics::Surface *s = _flicPlayer.decodeNextFrame();
|
||
if (s) {
|
||
_graph->drawTransparentSurface(_graph->_frontScreen, 0, 0, s, 255);
|
||
_graph->change();
|
||
_flcFrameSurface = s;
|
||
} else if (_flicLooped) {
|
||
_flicPlayer.rewind();
|
||
playNextFLCFrame();
|
||
} else if (_flcFrameSurface) {
|
||
_graph->drawTransparentSurface(_graph->_frontScreen, 0, 0, _flcFrameSurface, 255);
|
||
_graph->change();
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
void PrinceEngine::loadMobTranslationTexts() {
|
||
Common::SeekableReadStream *mobTranslationStream = SearchMan.createReadStreamForMember("mob_translate.dat");
|
||
if (!mobTranslationStream) {
|
||
error("Can't load mob_translate.dat");
|
||
}
|
||
_mobTranslationSize = mobTranslationStream->size();
|
||
_mobTranslationData = (byte *)malloc(_mobTranslationSize);
|
||
mobTranslationStream->read(_mobTranslationData, _mobTranslationSize);
|
||
delete mobTranslationStream;
|
||
}
|
||
|
||
void PrinceEngine::setMobTranslationTexts() {
|
||
int locationOffset = READ_LE_UINT16(_mobTranslationData + (_locationNr - 1) * 2);
|
||
if (locationOffset) {
|
||
byte *locationText = _mobTranslationData + locationOffset;
|
||
for (uint i = 0; i < _mobList.size(); i++) {
|
||
byte c;
|
||
locationText++;
|
||
_mobList[i]._name.clear();
|
||
while ((c = *locationText)) {
|
||
_mobList[i]._name += c;
|
||
locationText++;
|
||
}
|
||
locationText++;
|
||
_mobList[i]._examText.clear();
|
||
c = *locationText;
|
||
locationText++;
|
||
if (c) {
|
||
_mobList[i]._examText += c;
|
||
do {
|
||
c = *locationText;
|
||
_mobList[i]._examText += c;
|
||
locationText++;
|
||
} while (c != 255);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void PrinceEngine::keyHandler(Common::Event event) {
|
||
uint16 nChar = event.customType;
|
||
switch (nChar) {
|
||
case kActionSave:
|
||
if (canLoadGameStateCurrently())
|
||
scummVMSaveLoadDialog(false);
|
||
break;
|
||
case kActionLoad:
|
||
if (canSaveGameStateCurrently())
|
||
scummVMSaveLoadDialog(true);
|
||
break;
|
||
case kActionZ: // This refers to the "z" key on the keyboard. It is used to play a prison escape mini-game near the end of the game.
|
||
if (_flags->getFlagValue(Flags::POWERENABLED)) {
|
||
_flags->setFlagValue(Flags::MBFLAG, 1);
|
||
}
|
||
break;
|
||
case kActionX: // This refers to the "x" key on the keyboard. It is used to play a prison escape mini-game near the end of the game.
|
||
if (_flags->getFlagValue(Flags::POWERENABLED)) {
|
||
_flags->setFlagValue(Flags::MBFLAG, 2);
|
||
}
|
||
break;
|
||
case kActionSkip:
|
||
if (_intro) {
|
||
stopTextToSpeech();
|
||
_intro = false;
|
||
}
|
||
|
||
_flags->setFlagValue(Flags::ESCAPED2, 1);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
void PrinceEngine::printAt(uint32 slot, uint8 color, char *s, uint16 x, uint16 y) {
|
||
debugC(1, DebugChannel::kEngine, "PrinceEngine::printAt slot %d, color %d, x %02d, y %02d, str %s", slot, color, x, y, s);
|
||
|
||
if (getLanguage() == Common::DE_DEU)
|
||
correctStringDEU(s);
|
||
|
||
// Cutscene
|
||
if (slot == 9) {
|
||
setTTSVoice(color);
|
||
sayText(s, true, Common::TextToSpeechManager::QUEUE);
|
||
} else {
|
||
bool printText = true;
|
||
bool isSpeech = false;
|
||
|
||
if (slot == 10) {
|
||
if (_locationNr == 50) { // Map
|
||
if (_printMapNotification) {
|
||
_printMapNotification = false;
|
||
} else {
|
||
printText = false;
|
||
}
|
||
} else {
|
||
isSpeech = true;
|
||
}
|
||
} else if (slot == 0) {
|
||
isSpeech = true;
|
||
}
|
||
|
||
if (printText) {
|
||
setTTSVoice(color);
|
||
sayText(s, isSpeech);
|
||
}
|
||
}
|
||
|
||
Text &text = _textSlots[slot];
|
||
text._str = s;
|
||
text._x = x;
|
||
text._y = y;
|
||
text._color = color;
|
||
int lines = calcTextLines(s);
|
||
text._time = calcTextTime(lines);
|
||
}
|
||
|
||
int PrinceEngine::calcTextLines(const char *s) {
|
||
int lines = 1;
|
||
while (*s) {
|
||
if (*s == '\n') {
|
||
lines++;
|
||
}
|
||
s++;
|
||
}
|
||
return lines;
|
||
}
|
||
|
||
int PrinceEngine::calcTextTime(int numberOfLines) {
|
||
return numberOfLines * 30;
|
||
}
|
||
|
||
void PrinceEngine::correctStringDEU(char *s) {
|
||
while (*s) {
|
||
switch (*s) {
|
||
case '\xc4':
|
||
*s = '\x83';
|
||
break;
|
||
case '\xd6':
|
||
*s = '\x84';
|
||
break;
|
||
case '\xdc':
|
||
*s = '\x85';
|
||
break;
|
||
case '\xdf':
|
||
*s = '\x7f';
|
||
break;
|
||
case '\xe4':
|
||
*s = '\x80';
|
||
break;
|
||
case '\xf6':
|
||
*s = '\x81';
|
||
break;
|
||
case '\xfc':
|
||
*s = '\x82';
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
s++;
|
||
}
|
||
}
|
||
|
||
uint32 PrinceEngine::getTextWidth(const char *s) {
|
||
uint16 textW = 0;
|
||
while (*s) {
|
||
textW += _font->getCharWidth(*s) + _font->getKerningOffset(0, 0);
|
||
s++;
|
||
}
|
||
return textW;
|
||
}
|
||
|
||
void PrinceEngine::showTexts(Graphics::Surface *screen) {
|
||
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
|
||
|
||
for (uint32 slot = 0; slot < kMaxTexts; slot++) {
|
||
|
||
if (_showInventoryFlag && slot) {
|
||
// only slot 0 for inventory
|
||
break;
|
||
}
|
||
|
||
Text& text = _textSlots[slot];
|
||
if (!text._str && !text._time) {
|
||
continue;
|
||
}
|
||
|
||
int x = text._x;
|
||
int y = text._y;
|
||
|
||
if (!_showInventoryFlag) {
|
||
x -= _picWindowX;
|
||
y -= _picWindowY;
|
||
}
|
||
|
||
Common::Array<Common::String> lines;
|
||
_font->wordWrapText(text._str, _graph->_frontScreen->w, lines);
|
||
|
||
int wideLine = 0;
|
||
for (uint i = 0; i < lines.size(); i++) {
|
||
int textLen = getTextWidth(lines[i].c_str());
|
||
if (textLen > wideLine) {
|
||
wideLine = textLen;
|
||
}
|
||
}
|
||
|
||
int leftBorderText = 6;
|
||
if (x + wideLine / 2 > kNormalWidth - leftBorderText) {
|
||
x = kNormalWidth - leftBorderText - wideLine / 2;
|
||
}
|
||
|
||
if (x - wideLine / 2 < leftBorderText) {
|
||
x = leftBorderText + wideLine / 2;
|
||
}
|
||
|
||
int textSkip = 2;
|
||
for (uint i = 0; i < lines.size(); i++) {
|
||
int drawX = x - getTextWidth(lines[i].c_str()) / 2;
|
||
int drawY = y - 10 - (lines.size() - i) * (_font->getFontHeight() - textSkip);
|
||
if (drawX < 0) {
|
||
drawX = 0;
|
||
}
|
||
if (drawY < 0) {
|
||
drawY = 0;
|
||
}
|
||
_font->drawString(screen, lines[i], drawX, drawY, screen->w, text._color);
|
||
}
|
||
|
||
text._time--;
|
||
if (!text._time) {
|
||
if (ttsMan != nullptr && (ConfMan.getBool("tts_enabled_speech") ||
|
||
ConfMan.getBool("tts_enabled_objects") || ConfMan.getBool("tts_enabled_missing_voice")) && ttsMan->isSpeaking()) {
|
||
text._time = 1;
|
||
continue;
|
||
}
|
||
text._str = nullptr;
|
||
}
|
||
}
|
||
}
|
||
|
||
void PrinceEngine::sayText(const Common::String &text, bool isSpeech, Common::TextToSpeechManager::Action action) {
|
||
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
|
||
// Only voice subtitles if either this is a version with no voices or the speech volume is muted (the English/Spanish
|
||
// translations still have dubs in different languages, so don't voice the subtitles unless the dub is muted)
|
||
bool speak = (!isSpeech && ConfMan.getBool("tts_enabled_objects")) ||
|
||
(isSpeech && ConfMan.getBool("tts_enabled_speech") &&
|
||
(getFeatures() & GF_NOVOICES || ConfMan.getInt("speech_volume") == 0 || ConfMan.getBool("subtitles")));
|
||
if (ttsMan != nullptr && speak) {
|
||
Common::String ttsText(text);
|
||
// Some emotive text has a < at the front, which causes the entire text to not be voiced by the TTS system
|
||
// Text with quotation marks also contains \ as an escape character, which is awkwardly voiced by TTS if not
|
||
// removed
|
||
ttsText.replace('\n', ' ');
|
||
ttsText.replace('<', ' ');
|
||
ttsText.replace('\\', ' ');
|
||
#ifdef USE_TTS
|
||
ttsMan->say(convertText(ttsText), action);
|
||
#endif
|
||
}
|
||
}
|
||
|
||
#ifdef USE_TTS
|
||
|
||
Common::U32String PrinceEngine::convertText(const Common::String &text) const {
|
||
const uint16 *conversionTable;
|
||
|
||
switch (getLanguage()) {
|
||
case Common::EN_ANY: // Some of the English text has a few Polish characters
|
||
case Common::PL_POL:
|
||
conversionTable = polishEncodingTable;
|
||
break;
|
||
case Common::RU_RUS:
|
||
if (getFeatures() & GF_RUSPROJEDITION) {
|
||
return Common::U32String(text, Common::CodePage::kDos866);
|
||
}
|
||
|
||
conversionTable = russianEncodingTable;
|
||
break;
|
||
case Common::DE_DEU:
|
||
conversionTable = germanEncodingTable;
|
||
break;
|
||
case Common::ES_ESP:
|
||
conversionTable = spanishEncodingTable;
|
||
break;
|
||
default:
|
||
conversionTable = polishEncodingTable;
|
||
}
|
||
|
||
const byte *bytes = (const byte *)text.c_str();
|
||
byte *convertedBytes = new byte[text.size() * 2 + 1];
|
||
|
||
int i = 0;
|
||
for (const byte *b = bytes; *b; ++b) {
|
||
bool inTable = checkConversionTable(b, i, convertedBytes, conversionTable);
|
||
|
||
if (_credits && !inTable) {
|
||
if (*b == 0x2a) { // * in credits
|
||
convertedBytes[i] = 0x20;
|
||
i++;
|
||
continue;
|
||
}
|
||
|
||
if (*b == 0x23) {
|
||
i++;
|
||
break;
|
||
}
|
||
|
||
// Credits in other languages may have some Polish characters
|
||
inTable = checkConversionTable(b, i, convertedBytes, polishEncodingTable);
|
||
}
|
||
|
||
if (!inTable) {
|
||
convertedBytes[i] = *b;
|
||
i++;
|
||
}
|
||
}
|
||
|
||
convertedBytes[i] = 0;
|
||
|
||
Common::U32String result((char *)convertedBytes);
|
||
delete[] convertedBytes;
|
||
|
||
return result;
|
||
}
|
||
|
||
bool PrinceEngine::checkConversionTable(const byte *character, int &index, byte *convertedBytes, const uint16 *table) const {
|
||
for (int i = 0; table[i]; i += 2) {
|
||
if (*character == table[i]) {
|
||
convertedBytes[index] = (table[i + 1] >> 8) & 0xff;
|
||
convertedBytes[index + 1] = table[i + 1] & 0xff;
|
||
index += 2;
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
#endif
|
||
|
||
void PrinceEngine::setTTSVoice(uint8 textColor) const {
|
||
#ifdef USE_TTS
|
||
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
|
||
if (ttsMan != nullptr && (ConfMan.getBool("tts_enabled_speech") || ConfMan.getBool("tts_enabled_objects") || ConfMan.getBool("tts_enabled_missing_voice"))) {
|
||
int id = 0;
|
||
|
||
for (int i = 0; i < kCharacterVoiceDataCount; ++i) {
|
||
// In many cases, characters can be differentiated by just the text color, but sometimes
|
||
// there may be different characters with the same text colors in different locations, and rarely
|
||
// different characters with the same text colors in the same location. Using the location number and/or
|
||
// mob index differentiates characters in these cases
|
||
if (characterVoiceData[i].textColor == textColor &&
|
||
(characterVoiceData[i].locationNumber == 0 || characterVoiceData[i].locationNumber == _locationNr) &&
|
||
(characterVoiceData[i].mobIndex == -1 || characterVoiceData[i].mobIndex == _dialogMob)) {
|
||
id = i;
|
||
break;
|
||
}
|
||
}
|
||
|
||
Common::Array<int> voices;
|
||
int pitch = 0;
|
||
Common::TTSVoice::Gender gender;
|
||
|
||
if (characterVoiceData[id].male) {
|
||
voices = ttsMan->getVoiceIndicesByGender(Common::TTSVoice::MALE);
|
||
gender = Common::TTSVoice::MALE;
|
||
} else {
|
||
voices = ttsMan->getVoiceIndicesByGender(Common::TTSVoice::FEMALE);
|
||
gender = Common::TTSVoice::FEMALE;
|
||
}
|
||
|
||
// If no voice is available for the necessary gender, set the voice to default
|
||
if (voices.empty()) {
|
||
ttsMan->setVoice(0);
|
||
} else {
|
||
int voiceIndex = characterVoiceData[id].voiceID % voices.size();
|
||
ttsMan->setVoice(voices[voiceIndex]);
|
||
}
|
||
|
||
// If no voices are available for this gender, alter the pitch to mimic a voice
|
||
// of the other gender
|
||
if (ttsMan->getVoice().getGender() != gender) {
|
||
if (gender == Common::TTSVoice::MALE) {
|
||
pitch -= 50;
|
||
} else {
|
||
pitch += 50;
|
||
}
|
||
}
|
||
|
||
ttsMan->setPitch(pitch);
|
||
}
|
||
#endif
|
||
}
|
||
|
||
void PrinceEngine::stopTextToSpeech() const {
|
||
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
|
||
if (ttsMan != nullptr && (ConfMan.getBool("tts_enabled_objects") || ConfMan.getBool("tts_enabled_speech") || ConfMan.getBool("tts_enabled_missing_voice")) &&
|
||
ttsMan->isSpeaking()) {
|
||
ttsMan->stop();
|
||
}
|
||
}
|
||
|
||
void PrinceEngine::pausePrinceEngine(int fps) {
|
||
int delay = 1000 / fps - int32(_system->getMillis() - _currentTime);
|
||
delay = delay < 0 ? 0 : delay;
|
||
_system->delayMillis(delay);
|
||
_currentTime = _system->getMillis();
|
||
}
|
||
|
||
void PrinceEngine::leftMouseButton() {
|
||
_flags->setFlagValue(Flags::ESCAPED2, 1); // skip intro animation
|
||
_flags->setFlagValue(Flags::LMOUSE, 1);
|
||
if (_flags->getFlagValue(Flags::POWERENABLED)) {
|
||
_flags->setFlagValue(Flags::MBFLAG, 1);
|
||
}
|
||
if (_mouseFlag) {
|
||
int option = 0;
|
||
int optionEvent = -1;
|
||
|
||
if (_optionsFlag) {
|
||
if (_optionEnabled < _optionsNumber && _optionEnabled != -1) {
|
||
option = _optionEnabled;
|
||
_optionsFlag = 0;
|
||
} else {
|
||
return;
|
||
}
|
||
} else {
|
||
_optionsMob = _selectedMob;
|
||
if (_optionsMob == -1) {
|
||
walkTo();
|
||
return;
|
||
}
|
||
option = 0;
|
||
}
|
||
//do_option
|
||
if (_currentPointerNumber != 2) {
|
||
//skip_use_code
|
||
int optionScriptOffset = _room->getOptionOffset(option);
|
||
if (optionScriptOffset != 0) {
|
||
optionEvent = _script->scanMobEvents(_optionsMob, optionScriptOffset);
|
||
}
|
||
if (optionEvent == -1) {
|
||
if (!option) {
|
||
walkTo();
|
||
return;
|
||
} else {
|
||
optionEvent = _script->getOptionStandardOffset(option);
|
||
}
|
||
}
|
||
} else if (_selectedMode) {
|
||
//give_item
|
||
if (_room->_itemGive) {
|
||
optionEvent = _script->scanMobEventsWithItem(_optionsMob, _room->_itemGive, _selectedItem);
|
||
}
|
||
if (optionEvent == -1) {
|
||
//standard_giveitem
|
||
optionEvent = _script->_scriptInfo.stdGiveItem;
|
||
}
|
||
} else {
|
||
if (_room->_itemUse) {
|
||
optionEvent = _script->scanMobEventsWithItem(_optionsMob, _room->_itemUse, _selectedItem);
|
||
_flags->setFlagValue(Flags::SELITEM, _selectedItem);
|
||
}
|
||
if (optionEvent == -1) {
|
||
//standard_useitem
|
||
optionEvent = _script->_scriptInfo.stdUseItem;
|
||
}
|
||
}
|
||
_interpreter->storeNewPC(optionEvent);
|
||
_flags->setFlagValue(Flags::CURRMOB, _selectedMob);
|
||
_dialogMob = _selectedMob;
|
||
_selectedMob = -1;
|
||
_optionsMob = -1;
|
||
} else {
|
||
if (_intro) {
|
||
stopTextToSpeech();
|
||
_intro = false;
|
||
}
|
||
|
||
if (!_flags->getFlagValue(Flags::POWERENABLED)) {
|
||
if (!_flags->getFlagValue(Flags::NOCLSTEXT)) {
|
||
for (int slot = 0; slot < kMaxTexts; slot++) {
|
||
if (slot != 9) {
|
||
Text& text = _textSlots[slot];
|
||
if (!text._str) {
|
||
continue;
|
||
}
|
||
stopTextToSpeech();
|
||
text._str = nullptr;
|
||
text._time = 0;
|
||
}
|
||
}
|
||
_mainHero->_talkTime = 0;
|
||
_secondHero->_talkTime = 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void PrinceEngine::rightMouseButton() {
|
||
if (_flags->getFlagValue(Flags::POWERENABLED)) {
|
||
_flags->setFlagValue(Flags::MBFLAG, 2);
|
||
}
|
||
if (_mouseFlag && _mouseFlag != 3) {
|
||
_mainHero->freeOldMove();
|
||
_secondHero->freeOldMove();
|
||
_interpreter->storeNewPC(0);
|
||
if (_currentPointerNumber < 2) {
|
||
enableOptions(true);
|
||
} else {
|
||
_currentPointerNumber = 1;
|
||
changeCursor(1);
|
||
}
|
||
}
|
||
}
|
||
|
||
void PrinceEngine::createDialogBox(int dialogBoxNr) {
|
||
_dialogLines = 0;
|
||
int amountOfDialogOptions = 0;
|
||
int dialogDataValue = (int)READ_LE_UINT32(_dialogData);
|
||
|
||
byte c;
|
||
int sentenceNumber;
|
||
_dialogText = _dialogBoxAddr[dialogBoxNr];
|
||
byte *dialogText = _dialogText;
|
||
|
||
while ((sentenceNumber = *dialogText) != 0xFF) {
|
||
dialogText++;
|
||
if (!(dialogDataValue & (1 << sentenceNumber))) {
|
||
_dialogLines += calcTextLines((const char *)dialogText);
|
||
amountOfDialogOptions++;
|
||
}
|
||
do {
|
||
c = *dialogText;
|
||
dialogText++;
|
||
} while (c);
|
||
}
|
||
|
||
_dialogHeight = _font->getFontHeight() * _dialogLines + _dialogLineSpace * (amountOfDialogOptions + 1);
|
||
_dialogImage = new Graphics::Surface();
|
||
_dialogImage->create(_dialogWidth, _dialogHeight, Graphics::PixelFormat::createFormatCLUT8());
|
||
Common::Rect dBoxRect(0, 0, _dialogWidth, _dialogHeight);
|
||
_dialogImage->fillRect(dBoxRect, _graph->kShadowColor);
|
||
}
|
||
|
||
void PrinceEngine::dialogRun() {
|
||
|
||
_dialogFlag = true;
|
||
setTTSVoice(kHeroTextColor);
|
||
|
||
while (!shouldQuit()) {
|
||
|
||
_interpreter->stepBg();
|
||
drawScreen();
|
||
|
||
int dialogX = (640 - _dialogWidth) / 2;
|
||
int dialogY = 460 - _dialogHeight;
|
||
_graph->drawAsShadowSurface(_graph->_frontScreen, dialogX, dialogY, _dialogImage, _graph->_shadowTable50);
|
||
|
||
int dialogSkipLeft = 14;
|
||
int dialogSkipUp = 10;
|
||
|
||
int dialogTextX = dialogX + dialogSkipLeft;
|
||
int dialogTextY = dialogY + dialogSkipUp;
|
||
|
||
Common::Point mousePos = _system->getEventManager()->getMousePos();
|
||
|
||
byte c;
|
||
int sentenceNumber;
|
||
byte *dialogText = _dialogText;
|
||
byte *dialogCurrentText = nullptr;
|
||
int dialogSelected = -1;
|
||
int dialogDataValue = (int)READ_LE_UINT32(_dialogData);
|
||
|
||
while ((sentenceNumber = *dialogText) != 0xFF) {
|
||
dialogText++;
|
||
int actualColor = _dialogColor1;
|
||
|
||
if (!(dialogDataValue & (1 << sentenceNumber))) {
|
||
if (getLanguage() == Common::DE_DEU) {
|
||
correctStringDEU((char *)dialogText);
|
||
}
|
||
Common::Array<Common::String> lines;
|
||
_font->wordWrapText((const char *)dialogText, _graph->_frontScreen->w, lines);
|
||
|
||
Common::Rect dialogOption(dialogTextX, dialogTextY - dialogSkipUp / 2, dialogX + _dialogWidth - dialogSkipLeft, dialogTextY + lines.size() * _font->getFontHeight() + dialogSkipUp / 2 - 1);
|
||
if (dialogOption.contains(mousePos)) {
|
||
actualColor = _dialogColor2;
|
||
dialogSelected = sentenceNumber;
|
||
dialogCurrentText = dialogText;
|
||
|
||
if (_previousSelectedDialog != dialogSelected) {
|
||
sayText((const char *)dialogCurrentText, false);
|
||
_previousSelectedDialog = dialogSelected;
|
||
}
|
||
}
|
||
|
||
for (uint j = 0; j < lines.size(); j++) {
|
||
_font->drawString(_graph->_frontScreen, lines[j], dialogTextX, dialogTextY, _graph->_frontScreen->w, actualColor);
|
||
dialogTextY += _font->getFontHeight();
|
||
}
|
||
dialogTextY += _dialogLineSpace;
|
||
}
|
||
do {
|
||
c = *dialogText;
|
||
dialogText++;
|
||
} while (c);
|
||
}
|
||
|
||
if (dialogSelected == -1) {
|
||
_previousSelectedDialog = -1;
|
||
}
|
||
|
||
Common::Event event;
|
||
Common::EventManager *eventMan = _system->getEventManager();
|
||
while (eventMan->pollEvent(event)) {
|
||
switch (event.type) {
|
||
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
|
||
keyHandler(event);
|
||
break;
|
||
case Common::EVENT_LBUTTONDOWN:
|
||
if (dialogSelected != -1) {
|
||
dialogLeftMouseButton(dialogCurrentText, dialogSelected);
|
||
_dialogFlag = false;
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (shouldQuit()) {
|
||
return;
|
||
}
|
||
|
||
if (!_dialogFlag) {
|
||
break;
|
||
}
|
||
|
||
|
||
_graph->update(_graph->_frontScreen);
|
||
pausePrinceEngine();
|
||
}
|
||
_dialogImage->free();
|
||
delete _dialogImage;
|
||
_dialogImage = nullptr;
|
||
_dialogFlag = false;
|
||
}
|
||
|
||
void PrinceEngine::dialogLeftMouseButton(byte *string, int dialogSelected) {
|
||
_interpreter->setString(string);
|
||
talkHero(0);
|
||
|
||
int dialogDataValue = (int)READ_LE_UINT32(_dialogData);
|
||
dialogDataValue |= (1u << dialogSelected);
|
||
WRITE_LE_UINT32(_dialogData, dialogDataValue);
|
||
|
||
_flags->setFlagValue(Flags::BOXSEL, dialogSelected + 1);
|
||
setVoice(0, 28, dialogSelected + 1);
|
||
|
||
_flags->setFlagValue(Flags::VOICE_H_LINE, _dialogOptLines[dialogSelected * 4]);
|
||
_flags->setFlagValue(Flags::VOICE_A_LINE, _dialogOptLines[dialogSelected * 4 + 1]);
|
||
_flags->setFlagValue(Flags::VOICE_B_LINE, _dialogOptLines[dialogSelected * 4 + 2]);
|
||
|
||
_interpreter->setString(_dialogOptAddr[dialogSelected]);
|
||
}
|
||
|
||
void PrinceEngine::talkHero(int slot) {
|
||
// heroSlot = textSlot (slot 0 or 1)
|
||
Text &text = _textSlots[slot];
|
||
int lines = calcTextLines((const char *)_interpreter->getString());
|
||
int time = lines * 30;
|
||
|
||
if (slot == 0) {
|
||
text._color = 220; // TODO - test this
|
||
_mainHero->_state = Hero::kHeroStateTalk;
|
||
_mainHero->_talkTime = time;
|
||
text._x = _mainHero->_middleX;
|
||
text._y = _mainHero->_middleY - _mainHero->_scaledFrameYSize;
|
||
} else {
|
||
text._color = _flags->getFlagValue(Flags::KOLOR); // TODO - test this
|
||
_secondHero->_state = Hero::kHeroStateTalk;
|
||
_secondHero->_talkTime = time;
|
||
text._x = _secondHero->_middleX;
|
||
text._y = _secondHero->_middleY - _secondHero->_scaledFrameYSize;
|
||
}
|
||
text._time = time;
|
||
if (getLanguage() == Common::DE_DEU) {
|
||
correctStringDEU((char *)_interpreter->getString());
|
||
}
|
||
text._str = (const char *)_interpreter->getString();
|
||
|
||
setTTSVoice(text._color);
|
||
sayText(text._str, true);
|
||
|
||
_interpreter->increaseString();
|
||
}
|
||
|
||
void PrinceEngine::getCurve() {
|
||
_flags->setFlagValue(Flags::TORX1, _curveData[_curvPos]);
|
||
_flags->setFlagValue(Flags::TORY1, _curveData[_curvPos + 1]);
|
||
_curvPos += 2;
|
||
}
|
||
|
||
void PrinceEngine::makeCurve() {
|
||
_curvPos = 0;
|
||
int x1 = _flags->getFlagValue(Flags::TORX1);
|
||
int y1 = _flags->getFlagValue(Flags::TORY1);
|
||
int x2 = _flags->getFlagValue(Flags::TORX2);
|
||
int y2 = _flags->getFlagValue(Flags::TORY2);
|
||
|
||
for (int i = 0; i < kCurveLen; i++) {
|
||
int sum1 = x1 * curveValues[i][0];
|
||
sum1 += (x2 + (x1 - x2) / 2) * curveValues[i][1];
|
||
sum1 += x2 * curveValues[i][2];
|
||
sum1 += x2 * curveValues[i][3];
|
||
|
||
int sum2 = y1 * curveValues[i][0];
|
||
sum2 += (y2 - 20) * curveValues[i][1];
|
||
sum2 += (y2 - 10) * curveValues[i][2];
|
||
sum2 += y2 * curveValues[i][3];
|
||
|
||
_curveData[i * 2] = (sum1 >> 15);
|
||
_curveData[i * 2 + 1] = (sum2 >> 15);
|
||
}
|
||
}
|
||
|
||
void PrinceEngine::mouseWeirdo() {
|
||
if (_mouseFlag == 3) {
|
||
int weirdDir = _randomSource.getRandomNumber(3);
|
||
Common::Point mousePos = _system->getEventManager()->getMousePos();
|
||
switch (weirdDir) {
|
||
case 0:
|
||
mousePos.x += kCelStep;
|
||
break;
|
||
case 1:
|
||
mousePos.x -= kCelStep;
|
||
break;
|
||
case 2:
|
||
mousePos.y += kCelStep;
|
||
break;
|
||
case 3:
|
||
mousePos.y -= kCelStep;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
mousePos.x = CLIP(mousePos.x, (int16) 315, (int16) 639);
|
||
_flags->setFlagValue(Flags::MXFLAG, mousePos.x);
|
||
mousePos.y = CLIP(mousePos.y, (int16) 0, (int16) 170);
|
||
_flags->setFlagValue(Flags::MYFLAG, mousePos.y);
|
||
_system->warpMouse(mousePos.x, mousePos.y);
|
||
}
|
||
}
|
||
|
||
void PrinceEngine::showPower() {
|
||
if (_flags->getFlagValue(Flags::POWERENABLED)) {
|
||
int power = _flags->getFlagValue(Flags::POWER);
|
||
|
||
byte *dst = (byte *)_graph->_frontScreen->getBasePtr(kPowerBarPosX, kPowerBarPosY);
|
||
for (int y = 0; y < kPowerBarHeight; y++) {
|
||
byte *dst2 = dst;
|
||
for (int x = 0; x < kPowerBarWidth; x++, dst2++) {
|
||
*dst2 = kPowerBarBackgroundColor;
|
||
}
|
||
dst += _graph->_frontScreen->pitch;
|
||
}
|
||
|
||
if (power) {
|
||
dst = (byte *)_graph->_frontScreen->getBasePtr(kPowerBarPosX, kPowerBarGreenPosY);
|
||
for (int y = 0; y < kPowerBarGreenHeight; y++) {
|
||
byte *dst2 = dst;
|
||
for (int x = 0; x < power + 1; x++, dst2++) {
|
||
if (x < 58) {
|
||
*dst2 = kPowerBarGreenColor1;
|
||
} else {
|
||
*dst2 = kPowerBarGreenColor2;
|
||
}
|
||
}
|
||
dst += _graph->_frontScreen->pitch;
|
||
}
|
||
}
|
||
|
||
_graph->change();
|
||
}
|
||
}
|
||
|
||
void PrinceEngine::scrollCredits() {
|
||
byte *scrollAdress = _creditsData;
|
||
|
||
_credits = true;
|
||
setTTSVoice(kNarratorTextColor);
|
||
if (getLanguage() == Common::DE_DEU) {
|
||
correctStringDEU((char *)scrollAdress);
|
||
}
|
||
sayText((char *)scrollAdress, false, Common::TextToSpeechManager::INTERRUPT);
|
||
|
||
while (!shouldQuit()) {
|
||
for (int scrollPos = 0; scrollPos > -23; scrollPos--) {
|
||
const Graphics::Surface *roomSurface = _roomBmp->getSurface();
|
||
if (roomSurface) {
|
||
_graph->draw(_graph->_frontScreen, roomSurface);
|
||
}
|
||
char *s = (char *)scrollAdress;
|
||
int drawY = scrollPos;
|
||
for (int i = 0; i < 22; i++) {
|
||
Common::String line;
|
||
char *linePos = s;
|
||
while ((*linePos != 13)) {
|
||
line += *linePos;
|
||
linePos++;
|
||
}
|
||
if (!line.empty()) {
|
||
int drawX = (kNormalWidth - getTextWidth(line.c_str())) / 2;
|
||
_font->drawString(_graph->_frontScreen, line, drawX, drawY, _graph->_frontScreen->w, 217);
|
||
}
|
||
|
||
char letter1;
|
||
bool gotIt1 = false;
|
||
do {
|
||
letter1 = *s;
|
||
s++;
|
||
if (letter1 == 13) {
|
||
if (*s == 10) {
|
||
s++;
|
||
}
|
||
if (*s != 35) {
|
||
gotIt1 = true;
|
||
}
|
||
break;
|
||
}
|
||
} while (letter1 != 35);
|
||
|
||
if (gotIt1) {
|
||
drawY += 23;
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
Common::Event event;
|
||
Common::EventManager *eventMan = _system->getEventManager();
|
||
while (eventMan->pollEvent(event)) {
|
||
if (event.type == Common::EVENT_CUSTOM_ENGINE_ACTION_START) {
|
||
if (event.customType == kActionSkip) {
|
||
blackPalette();
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
if (shouldQuit()) {
|
||
return;
|
||
}
|
||
_graph->change();
|
||
_graph->update(_graph->_frontScreen);
|
||
pausePrinceEngine(kFPS * 2);
|
||
}
|
||
char letter2;
|
||
byte *scan2 = scrollAdress;
|
||
bool gotIt2 = false;
|
||
do {
|
||
letter2 = *scan2;
|
||
scan2++;
|
||
if (letter2 == 13) {
|
||
if (*scan2 == 10) {
|
||
scan2++;
|
||
}
|
||
if (*scan2 != 35) {
|
||
gotIt2 = true;
|
||
}
|
||
break;
|
||
}
|
||
} while (letter2 != 35);
|
||
if (gotIt2) {
|
||
scrollAdress = scan2;
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
blackPalette();
|
||
}
|
||
|
||
void PrinceEngine::mainLoop() {
|
||
changeCursor(0);
|
||
_currentTime = _system->getMillis();
|
||
|
||
while (!shouldQuit()) {
|
||
Common::Event event;
|
||
Common::EventManager *eventMan = _system->getEventManager();
|
||
while (eventMan->pollEvent(event)) {
|
||
switch (event.type) {
|
||
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
|
||
keyHandler(event);
|
||
break;
|
||
case Common::EVENT_LBUTTONDOWN:
|
||
leftMouseButton();
|
||
break;
|
||
case Common::EVENT_RBUTTONDOWN:
|
||
rightMouseButton();
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (shouldQuit()) {
|
||
return;
|
||
}
|
||
|
||
// for "throw a rock" mini-game
|
||
mouseWeirdo();
|
||
|
||
_interpreter->stepBg();
|
||
_interpreter->stepFg();
|
||
|
||
drawScreen();
|
||
|
||
_graph->update(_graph->_frontScreen);
|
||
|
||
openInventoryCheck();
|
||
|
||
pausePrinceEngine();
|
||
}
|
||
}
|
||
|
||
} // End of namespace Prince
|