aboutsummaryrefslogtreecommitdiff
path: root/engines/prince/prince.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/prince/prince.cpp')
-rw-r--r--engines/prince/prince.cpp707
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!: */