diff options
Diffstat (limited to 'engines/prince/prince.cpp')
-rw-r--r-- | engines/prince/prince.cpp | 707 |
1 files changed, 707 insertions, 0 deletions
diff --git a/engines/prince/prince.cpp b/engines/prince/prince.cpp new file mode 100644 index 0000000000..324c4592ae --- /dev/null +++ b/engines/prince/prince.cpp @@ -0,0 +1,707 @@ +/* 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/cursorman.h" +#include "graphics/surface.h" +#include "graphics/palette.h" +#include "graphics/pixelformat.h" + +#include "engines/util.h" +#include "engines/advancedDetector.h" + +#include "audio/audiostream.h" + +#include "prince/prince.h" +#include "prince/font.h" +#include "prince/graphics.h" +#include "prince/script.h" +#include "prince/debugger.h" +#include "prince/object.h" +#include "prince/mob.h" +#include "prince/sound.h" +#include "prince/variatxt.h" +#include "prince/flags.h" +#include "prince/font.h" +#include "prince/mhwanh.h" +#include "prince/cursor.h" +#include "prince/archive.h" +#include "prince/hero.h" +#include "prince/resource.h" +#include "prince/animation.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 frame %08ld %s", _frameNr, 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), + _cameraX(0), _newCameraX(0), _frameNr(0), _cursor1(nullptr), _cursor2(nullptr), _font(nullptr), + _walizkaBmp(nullptr), _roomBmp(nullptr), _cursorNr(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(_voiceStream, 0, sizeof(_voiceStream)); + + gDebugLevel = 10; +} + +PrinceEngine::~PrinceEngine() { + DebugMan.clearAllDebugChannels(); + + delete _rnd; + delete _debugger; + delete _cursor1; + delete _cursor2; + delete _midiPlayer; + delete _script; + delete _flags; + delete _interpreter; + delete _font; + delete _roomBmp; + delete _walizkaBmp; + delete _variaTxt; + delete[] _talkTxt; + delete _graph; + delete _mainHero; + delete _secondHero; + + for (uint32 i = 0; i < _objList.size(); ++i) { + delete _objList[i]; + } + _objList.clear(); +} + +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()); + + PtcArchive *all = new PtcArchive(); + if (!all->open("all/databank.ptc")) + error("Can't open all/databank.ptc"); + + PtcArchive *voices = new PtcArchive(); + if (!voices->open("data/voices/databank.ptc")) + error("Can't open data/voices/databank.ptc"); + + PtcArchive *sound = new PtcArchive(); + if (!sound->open("sound/databank.ptc")) + error("Can't open sound/databank.ptc"); + + SearchMan.addSubDirectoryMatching(gameDataDir, "all"); + + SearchMan.add("all", all); + SearchMan.add("voices", voices); + SearchMan.add("sound", sound); + + _graph = new GraphicsMan(this); + + _rnd = new Common::RandomSource("prince"); + _debugger = new Debugger(this); + + _midiPlayer = new MusicPlayer(this); + + _font = new Font(); + Resource::loadResource(_font, "font1.raw"); + + _walizkaBmp = new MhwanhDecoder(); + Resource::loadResource(_walizkaBmp, "walizka"); + + _script = new Script(); + Resource::loadResource(_script, "skrypt.dat"); + + _flags = new InterpreterFlags(); + _interpreter = new Interpreter(this, _script, _flags); + + _variaTxt = new VariaTxt(); + Resource::loadResource(_variaTxt, "variatxt.dat"); + + _cursor1 = new Cursor(); + Resource::loadResource(_cursor1, "mouse1.cur"); + + _cursor2 = new Cursor(); + Resource::loadResource(_cursor2, "mouse2.cur"); + + Common::SeekableReadStream *talkTxtStream = SearchMan.createReadStreamForMember("talktxt.dat"); + if (!talkTxtStream) { + error("Can't load talkTxtStream"); + return; + } + _talkTxtSize = talkTxtStream->size(); + _talkTxt = new byte[_talkTxtSize]; + talkTxtStream->read(_talkTxt, _talkTxtSize); + + delete talkTxtStream; + + _roomBmp = new Image::BitmapDecoder(); + + _mainHero = new Hero(); + _secondHero = new Hero(); + + _mainHero->loadAnimSet(0); +} + +void PrinceEngine::showLogo() { + MhwanhDecoder logo; + if (Resource::loadResource(&logo, "logo.raw")) { + _graph->setPalette(logo.getPalette()); + _graph->draw(0, 0, logo.getSurface()); + _graph->update(); + _system->delayMillis(700); + } +} + +Common::Error PrinceEngine::run() { + + init(); + + showLogo(); + + mainLoop(); + + return Common::kNoError; +} + +bool AnimListItem::loadFromStream(Common::SeekableReadStream &stream) { + int32 pos = stream.pos(); + + uint16 type = stream.readUint16LE(); + if (type == 0xFFFF) { + return false; + } + _type = type; + _fileNumber = stream.readUint16LE(); + _startPhase = stream.readUint16LE(); + _endPhase = stream.readUint16LE(); + _loopPhase = stream.readUint16LE(); + _x = stream.readSint16LE(); + _y = stream.readSint16LE(); + _loopType = stream.readUint16LE(); + _nextAnim = stream.readUint16LE(); + _flags = stream.readUint16LE(); + + debug("AnimListItem type %d, fileNumber %d, x %d, y %d, flags %d", _type, _fileNumber, _x, _y, _flags); + + + // 32 byte aligment + stream.seek(pos + 32); + + return true; +} + +bool PrinceEngine::loadLocation(uint16 locationNr) { + _flicPlayer.close(); + + memset(_textSlots, 0, sizeof(_textSlots)); + for(uint32 sampleId = 0; sampleId < MAX_SAMPLES; ++sampleId) { + stopSample(sampleId); + } + + debugEngine("PrinceEngine::loadLocation %d", locationNr); + const Common::FSNode gameDataDir(ConfMan.get("path")); + SearchMan.remove(Common::String::format("%02d", _locationNr)); + + _locationNr = locationNr; + _debugger->_locationNr = locationNr; + _cameraX = 0; + _newCameraX = 0; + + _flags->setFlagValue(Flags::CURRROOM, _locationNr); + _interpreter->stopBg(); + + changeCursor(0); + + const Common::String locationNrStr = Common::String::format("%02d", _locationNr); + debugEngine("loadLocation %s", locationNrStr.c_str()); + + PtcArchive *locationArchive = new PtcArchive(); + if (!locationArchive->open(locationNrStr + "/databank.ptc")) + error("Can't open location %s", locationNrStr.c_str()); + + SearchMan.add(locationNrStr, locationArchive); + + const char *musName = MusicPlayer::_musTable[MusicPlayer::_musRoomTable[locationNr]]; + _midiPlayer->loadMidi(musName); + + // load location background, replace old one + Resource::loadResource(_roomBmp, "room"); + if (_roomBmp->getSurface()) { + _sceneWidth = _roomBmp->getSurface()->w; + } + + _mainHero->_zoomBitmap->clear(); + Resource::loadResource(_mainHero->_zoomBitmap, "zoom", false); + + _mobList.clear(); + Resource::loadResource(_mobList, "mob.lst", false); + + for (uint32 i = 0; i < _objList.size(); ++i) { + delete _objList[i]; + } + _objList.clear(); + Resource::loadResource(_objList, "obj.lst", false); + + _animList.clear(); + Resource::loadResource(_animList, "anim.lst", false); + + return true; +} + +void PrinceEngine::changeCursor(uint16 curId) { + _debugger->_cursorNr = curId; + + const Graphics::Surface *curSurface = nullptr; + + uint16 hotspotX = 0; + uint16 hotspotY = 0; + + switch(curId) { + case 0: + CursorMan.showMouse(false); + return; + case 1: + curSurface = _cursor1->getSurface(); + break; + case 2: + curSurface = _cursor2->getSurface(); + hotspotX = curSurface->w >> 1; + hotspotY = curSurface->h >> 1; + break; + } + + CursorMan.replaceCursorPalette(_roomBmp->getPalette(), 0, 255); + CursorMan.replaceCursor( + curSurface->getBasePtr(0, 0), + curSurface->w, curSurface->h, + hotspotX, hotspotY, + 255, false, + &curSurface->format + ); + CursorMan.showMouse(true); +} + +bool PrinceEngine::playNextFrame() { + if (!_flicPlayer.isVideoLoaded()) + return false; + + const Graphics::Surface *s = _flicPlayer.decodeNextFrame(); + if (s) { + _graph->drawTransparent(0, 0, s); + _graph->change(); + } else if (_flicLooped) { + _flicPlayer.rewind(); + playNextFrame(); + } + + return true; +} + +void PrinceEngine::playSample(uint16 sampleId, uint16 loopType) { + if (_voiceStream[sampleId]) { + + if (_mixer->isSoundIDActive(sampleId)) { + return; + } + + Audio::AudioStream *audioStream = Audio::makeWAVStream(_voiceStream[sampleId], DisposeAfterUse::YES); + if (loopType) { + audioStream = new Audio::LoopingAudioStream((Audio::RewindableAudioStream*)audioStream, 0, DisposeAfterUse::NO); + } + _mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundHandle[sampleId], audioStream, sampleId); + } +} + +void PrinceEngine::stopSample(uint16 sampleId) { + _mixer->stopID(sampleId); + _voiceStream[sampleId] = nullptr; +} + +bool PrinceEngine::loadSample(uint32 sampleSlot, const Common::String &streamName) { + // FIXME: This is just a workaround streamName is a path + // SOUND\\SCIERKA1.WAV for now only last path component is used + Common::String normalizedPath = lastPathComponent(streamName, '\\'); + + debugEngine("loadSample slot %d, name %s", sampleSlot, normalizedPath.c_str()); + + _mixer->stopID(sampleSlot); + _voiceStream[sampleSlot] = nullptr; + _voiceStream[sampleSlot] = SearchMan.createReadStreamForMember(normalizedPath); + if (_voiceStream[sampleSlot] == nullptr) { + error("Can't load sample %s to slot %d", normalizedPath.c_str(), sampleSlot); + } + return _voiceStream[sampleSlot] == nullptr; +} + +bool PrinceEngine::loadVoice(uint32 slot, uint32 sampleSlot, const Common::String &streamName) { + debugEngine("Loading wav %s slot %d", streamName.c_str(), slot); + + if (slot > MAXTEXTS) { + error("Text slot bigger than MAXTEXTS %d", MAXTEXTS); + return false; + } + + _voiceStream[sampleSlot] = SearchMan.createReadStreamForMember(streamName); + if (!_voiceStream[sampleSlot]) { + error("Can't open %s", streamName.c_str()); + return false; + } + + uint32 id = _voiceStream[sampleSlot]->readUint32LE(); + if (id != 0x46464952) { + error("It's not RIFF file %s", streamName.c_str()); + return false; + } + + _voiceStream[sampleSlot]->skip(0x20); + id = _voiceStream[sampleSlot]->readUint32LE(); + if (id != 0x61746164) { + error("No data section in %s id %04x", streamName.c_str(), id); + return false; + } + + id = _voiceStream[sampleSlot]->readUint32LE(); + debugEngine("SetVoice slot %d time %04x", slot, id); + id <<= 3; + id /= 22050; + id += 2; + + _textSlots[slot]._time = id; + + debugEngine("SetVoice slot %d time %04x", slot, id); + _voiceStream[sampleSlot]->seek(0); + + return true; +} + +bool PrinceEngine::loadAnim(uint16 animNr, bool loop) { + Common::String streamName = Common::String::format("AN%02d", animNr); + Common::SeekableReadStream * flicStream = SearchMan.createReadStreamForMember(streamName); + + if (!flicStream) { + error("Can't open %s", streamName.c_str()); + return false; + } + + if (!_flicPlayer.loadStream(flicStream)) { + error("Can't load flic stream %s", streamName.c_str()); + } + + debugEngine("%s loaded", streamName.c_str()); + _flicLooped = loop; + _flicPlayer.start(); + playNextFrame(); + return true; +} + +void PrinceEngine::scrollCameraLeft(int16 delta) { + if (_newCameraX > 0) { + if (_newCameraX < delta) + _newCameraX = 0; + else + _newCameraX -= delta; + } +} + +void PrinceEngine::scrollCameraRight(int16 delta) { + if (_newCameraX != _sceneWidth - 640) { + if (_sceneWidth - 640 < delta + _newCameraX) + delta += (_sceneWidth - 640) - (delta + _newCameraX); + _newCameraX += delta; + debugEngine("PrinceEngine::scrollCameraRight() _newCameraX = %d; delta = %d", _newCameraX, delta); + } +} + +void PrinceEngine::keyHandler(Common::Event event) { + uint16 nChar = event.kbd.keycode; + switch (nChar) { + case Common::KEYCODE_d: + if (event.kbd.hasFlags(Common::KBD_CTRL)) { + getDebugger()->attach(); + } + break; + case Common::KEYCODE_LEFT: + scrollCameraLeft(32); + break; + case Common::KEYCODE_RIGHT: + scrollCameraRight(32); + break; + case Common::KEYCODE_ESCAPE: + _flags->setFlagValue(Flags::ESCAPED2, 1); + break; + case Common::KEYCODE_UP: + _mainHero->_phase++; + debugEngine("%d", _mainHero->_phase); + break; + case Common::KEYCODE_DOWN: + if(_mainHero->_phase > 0) { + _mainHero->_phase--; + } + debugEngine("%d", _mainHero->_phase); + break; + case Common::KEYCODE_w: + _mainHero->_lastDirection = _mainHero->UP; + debugEngine("UP"); + break; + case Common::KEYCODE_s: + _mainHero->_lastDirection = _mainHero->DOWN; + debugEngine("DOWN"); + break; + case Common::KEYCODE_a: + _mainHero->_lastDirection = _mainHero->LEFT; + debugEngine("LEFT"); + break; + case Common::KEYCODE_f: + _mainHero->_lastDirection = _mainHero->RIGHT; + debugEngine("RIGHT"); + break; + case Common::KEYCODE_1: + if(_mainHero->_state > 0) { + _mainHero->_state--; + } + debugEngine("%d", _mainHero->_state); + break; + case Common::KEYCODE_2: + _mainHero->_state++; + debugEngine("%d", _mainHero->_state); + break; + case Common::KEYCODE_i: + _mainHero->_middleY -= 10; + break; + case Common::KEYCODE_k: + _mainHero->_middleY += 10; + break; + case Common::KEYCODE_j: + _mainHero->_middleX -= 10; + break; + case Common::KEYCODE_l: + _mainHero->_middleX += 10; + break; + } +} + +void PrinceEngine::hotspot() { + Common::Point mousepos = _system->getEventManager()->getMousePos(); + Common::Point mousePosCamera(mousepos.x + _cameraX, mousepos.y); + + for (Common::Array<Mob>::const_iterator it = _mobList.begin() + ; it != _mobList.end() ; ++it) { + const Mob& mob = *it; + if (mob._visible) + continue; + if (mob._rect.contains(mousePosCamera)) { + uint16 textW = 0; + for (uint16 i = 0; i < mob._name.size(); ++i) + textW += _font->getCharWidth(mob._name[i]); + + uint16 x = mousepos.x - textW/2; + if (x > _graph->_frontScreen->w) + x = 0; + + if (x + textW > _graph->_frontScreen->w) + x = _graph->_frontScreen->w - textW; + + uint16 y = mousepos.y - _font->getFontHeight(); + if (y > _graph->_frontScreen->h) + y = _font->getFontHeight() - 2; + + _font->drawString( + _graph->_frontScreen, + mob._name, + x, + y, + _graph->_frontScreen->w, + 216 + ); + break; + } + } +} + +void PrinceEngine::printAt(uint32 slot, uint8 color, const 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); + + Text &text = _textSlots[slot]; + text._str = s; + text._x = x; + text._y = y; + text._color = color; +} + +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() { + for (uint32 slot = 0; slot < MAXTEXTS; ++slot) { + Text& text = _textSlots[slot]; + if (!text._str && !text._time) + continue; + + Common::Array<Common::String> lines; + _font->wordWrapText(text._str, _graph->_frontScreen->w, lines); + + for (uint8 i = 0; i < lines.size(); ++i) { + _font->drawString( + _graph->_frontScreen, + lines[i], + text._x - getTextWidth(lines[i].c_str())/2, + text._y - (lines.size() - i) * (_font->getFontHeight()), + _graph->_frontScreen->w, + text._color + ); + } + + --text._time; + if (text._time == 0) { + text._str = nullptr; + } + } +} + +void PrinceEngine::drawScreen() { + const Graphics::Surface *roomSurface = _roomBmp->getSurface(); + if (roomSurface) { + _graph->setPalette(_roomBmp->getPalette()); + const Graphics::Surface visiblePart = roomSurface->getSubArea(Common::Rect(_cameraX, 0, roomSurface->w, roomSurface->h)); + _graph->draw(0, 0, &visiblePart); + } + + if (_mainHero->_visible) { + const Graphics::Surface *mainHeroSurface = _mainHero->getSurface(); + + if (mainHeroSurface) + //_graph->drawTransparent(_mainHero->_middleX, _mainHero->_middleY, mainHeroSurface); + _graph->drawTransparent(_mainHero->_drawX, _mainHero->_drawY, mainHeroSurface); + } + + playNextFrame(); + + //if (_objectList) + // _graph->drawTransparent(_objectList->getSurface()); + + hotspot(); + + showTexts(); + + getDebugger()->onFrame(); + + _graph->update(); +} + +void PrinceEngine::mainLoop() { + + changeCursor(0); + + while (!shouldQuit()) { + uint32 currentTime = _system->getMillis(); + + 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_KEYUP: + break; + case Common::EVENT_MOUSEMOVE: + break; + case Common::EVENT_LBUTTONDOWN: + case Common::EVENT_RBUTTONDOWN: + break; + case Common::EVENT_LBUTTONUP: + case Common::EVENT_RBUTTONUP: + break; + case Common::EVENT_QUIT: + break; + default: + break; + } + } + + if (shouldQuit()) + return; + + // TODO: Update all structures, animations, naks, heros etc. + _mainHero -> showHero(); + + _interpreter->step(); + + drawScreen(); + + // Calculate the frame delay based off a desired frame time + int delay = 1000/15 - int32(_system->getMillis() - currentTime); + // Ensure non-negative + delay = delay < 0 ? 0 : delay; + _system->delayMillis(delay); + + _cameraX = _newCameraX; + ++_frameNr; + + if (_debugger->_locationNr != _locationNr) + loadLocation(_debugger->_locationNr); + if (_debugger->_cursorNr != _cursorNr) + changeCursor(_debugger->_cursorNr); + } +} + +} // End of namespace Prince + +/* vim: set tabstop=4 expandtab!: */ |