/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "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 { 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), _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), _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), _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) { // Debug/console setup DebugMan.addDebugChannel(DebugChannel::kScript, "script", "Prince Script debug channel"); DebugMan.addDebugChannel(DebugChannel::kEngine, "engine", "Prince Engine debug channel"); DebugMan.enableDebugChannel("script"); memset(_audioStream, 0, sizeof(_audioStream)); } PrinceEngine::~PrinceEngine() { DebugMan.clearAllDebugChannels(); delete _rnd; delete _debugger; 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; 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); } GUI::Debugger *PrinceEngine::getDebugger() { return _debugger; } void PrinceEngine::init() { const Common::FSNode gameDataDir(ConfMan.get("path")); debugEngine("Adding all path: %s", gameDataDir.getPath().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("all/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); _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(); } } 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()); 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_KEYDOWN: if (event.kbd.keycode == Common::KEYCODE_ESCAPE) { 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) { 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 (pause) { _midiPlayer->pause(); } else { _midiPlayer->resume(); } } void PrinceEngine::setShadowScale(int32 shadowScale) { shadowScale = 100 - shadowScale; if (!shadowScale) { _shadScaleValue = 10000; } else { _shadScaleValue = 10000 / shadowScale; } } void PrinceEngine::plotShadowLinePoint(int x, int y, int color, void *data) { PrinceEngine *vm = (PrinceEngine *)data; WRITE_LE_UINT16(&vm->_shadowLine[vm->_shadLineLen * 4], x); WRITE_LE_UINT16(&vm->_shadowLine[vm->_shadLineLen * 4 + 2], y); vm->_shadLineLen++; } 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_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.kbd.keycode; switch (nChar) { case Common::KEYCODE_F1: if (canLoadGameStateCurrently()) scummVMSaveLoadDialog(false); break; case Common::KEYCODE_F2: if (canSaveGameStateCurrently()) scummVMSaveLoadDialog(true); break; case Common::KEYCODE_d: if (event.kbd.hasFlags(Common::KBD_CTRL)) { getDebugger()->attach(); } break; case Common::KEYCODE_z: if (_flags->getFlagValue(Flags::POWERENABLED)) { _flags->setFlagValue(Flags::MBFLAG, 1); } break; case Common::KEYCODE_x: if (_flags->getFlagValue(Flags::POWERENABLED)) { _flags->setFlagValue(Flags::MBFLAG, 2); } break; case Common::KEYCODE_ESCAPE: _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); 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) { 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 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) { text._str = nullptr; } } } 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); _selectedMob = -1; _optionsMob = -1; } else { 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; } text._str = 0; 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; 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 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; } 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); } Common::Event event; Common::EventManager *eventMan = _system->getEventManager(); while (eventMan->pollEvent(event)) { switch (event.type) { case Common::EVENT_KEYDOWN: keyHandler(event); break; case Common::EVENT_LBUTTONDOWN: if (dialogSelected != -1) { dialogLeftMouseButton(dialogCurrentText, dialogSelected); _dialogFlag = false; } break; default: break; } } if (shouldQuit()) { return; } if (!_dialogFlag) { break; } getDebugger()->onFrame(); _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(); _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; 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_KEYDOWN) { if (event.kbd.keycode == Common::KEYCODE_ESCAPE) { 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_KEYDOWN: 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