/* 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 . * */ /* * This code is based on original Soltys source code * Copyright (c) 1994-1995 Janusz B. Wisniewski and L.K. Avalon */ #include "common/scummsys.h" #include "common/endian.h" #include "common/memstream.h" #include "common/savefile.h" #include "common/serializer.h" #include "common/str.h" #include "graphics/palette.h" #include "graphics/scaler.h" #include "graphics/thumbnail.h" #include "cge/vga13h.h" #include "cge/cge.h" #include "cge/cge_main.h" #include "cge/general.h" #include "cge/sound.h" #include "cge/snail.h" #include "cge/text.h" #include "cge/game.h" #include "cge/events.h" #include "cge/talk.h" #include "cge/vmenu.h" #include "cge/walk.h" #include "cge/sound.h" namespace CGE { const char *savegameStr = "SCUMMVM_CGE"; //-------------------------------------------------------------------------- const Dac g_stdPal[] = {// R G B { 0, 60, 0}, // 198 { 0, 104, 0}, // 199 { 20, 172, 0}, // 200 { 82, 82, 0}, // 201 { 0, 132, 82}, // 202 { 132, 173, 82}, // 203 { 82, 0, 0}, // 204 { 206, 0, 24}, // 205 { 255, 33, 33}, // 206 { 123, 41, 0}, // 207 { 0, 41, 0}, // 208 { 0, 0, 82}, // 209 { 132, 0, 0}, // 210 { 255, 0, 0}, // 211 { 255, 66, 66}, // 212 { 148, 66, 16}, // 213 { 0, 82, 0}, // 214 { 0, 0, 132}, // 215 { 173, 0, 0}, // 216 { 255, 49, 0}, // 217 { 255, 99, 99}, // 218 { 181, 107, 49}, // 219 { 0, 132, 0}, // 220 { 0, 0, 255}, // 221 { 173, 41, 0}, // 222 { 255, 82, 0}, // 223 { 255, 132, 132}, // 224 { 214, 148, 74}, // 225 { 41, 214, 0}, // 226 { 0, 82, 173}, // 227 { 255, 214, 0}, // 228 { 247, 132, 49}, // 229 { 255, 165, 165}, // 230 { 239, 198, 123}, // 231 { 173, 214, 0}, // 232 { 0, 132, 214}, // 233 { 57, 57, 57}, // 234 { 247, 189, 74}, // 235 { 255, 198, 198}, // 236 { 255, 239, 173}, // 237 { 214, 255, 173}, // 238 { 82, 173, 255}, // 239 { 107, 107, 107}, // 240 { 247, 222, 99}, // 241 { 255, 0, 255}, // 242 { 255, 132, 255}, // 243 { 132, 132, 173}, // 244 { 148, 247, 255}, // 245 { 148, 148, 148}, // 246 { 82, 0, 82}, // 247 { 112, 68, 112}, // 248 { 176, 88, 144}, // 249 { 214, 132, 173}, // 250 { 206, 247, 255}, // 251 { 198, 198, 198}, // 252 { 0, 214, 255}, // 253 { 96, 224, 96 }, // 254 { 255, 255, 255}, // 255 }; char *CGEEngine::mergeExt(char *buf, const char *name, const char *ext) { Common::strcpy_s(buf, kPathMax, name); char *dot = strrchr(buf, '.'); if (!dot) Common::strcat_s(buf, kPathMax, ext); return buf; } int CGEEngine::takeEnum(const char **tab, const char *text) { if (text) { for (const char **e = tab; *e; e++) { if (scumm_stricmp(text, *e) == 0) { return e - tab; } } } return -1; } int CGEEngine::newRandom(int range) { if (!range) return 0; return _randomSource.getRandomNumber(range - 1); } void CGEEngine::sndSetVolume() { // USeless for ScummVM } void CGEEngine::syncHeader(Common::Serializer &s) { debugC(1, kCGEDebugEngine, "CGEEngine::syncHeader(s)"); int i = kDemo; s.syncAsUint16LE(_now); s.syncAsUint16LE(_oldLev); s.syncAsUint16LE(i); // unused Demo string id for (i = 0; i < 5; i++) s.syncAsUint16LE(_game); s.syncAsSint16LE(i); // unused VGA::Mono variable s.syncAsUint16LE(_music); s.syncBytes(_volume, 2); for (i = 0; i < 4; i++) s.syncAsUint16LE(_flag[i]); if (s.isLoading()) { // Reset scene values initSceneValues(); } for (i = 0; i < kSceneMax; i++) { s.syncAsSint16LE(_heroXY[i].x); s.syncAsUint16LE(_heroXY[i].y); } for (i = 0; i < 1 + kSceneMax; i++) { s.syncAsByte(_barriers[i]._horz); s.syncAsByte(_barriers[i]._vert); } for (i = 0; i < kPocketNX; i++) s.syncAsUint16LE(_pocref[i]); if (s.isSaving()) { // Write checksum int checksum = kSavegameCheckSum; s.syncAsUint16LE(checksum); } else { // Read checksum and validate it uint16 checksum = 0; s.syncAsUint16LE(checksum); if (checksum != kSavegameCheckSum) error("%s", _text->getText(kBadSVG)); } } bool CGEEngine::loadGame(int slotNumber, SavegameHeader *header, bool tiny) { debugC(1, kCGEDebugEngine, "CGEEngine::loadgame(%d, header, %s)", slotNumber, tiny ? "true" : "false"); Common::MemoryReadStream *readStream; if (slotNumber == -1) { // Loading the data for the initial game state EncryptedStream file = EncryptedStream(_resman, kSavegame0Name); int size = file.size(); byte *dataBuffer = (byte *)malloc(size); file.read(dataBuffer, size); readStream = new Common::MemoryReadStream(dataBuffer, size, DisposeAfterUse::YES); } else { // Open up the savegame file Common::String slotName = getSaveStateName(slotNumber); Common::InSaveFile *saveFile = g_system->getSavefileManager()->openForLoading(slotName); // Read the data into a data buffer int size = saveFile->size(); byte *dataBuffer = (byte *)malloc(size); saveFile->read(dataBuffer, size); readStream = new Common::MemoryReadStream(dataBuffer, size, DisposeAfterUse::YES); delete saveFile; } // Check to see if it's a ScummVM savegame or not char buffer[kSavegameStrSize + 1]; readStream->read(buffer, kSavegameStrSize + 1); if (strncmp(buffer, savegameStr, kSavegameStrSize + 1) != 0) { // It's not, so rewind back to the start readStream->seek(0); if (header) // Header wanted where none exists, so return false return false; } else { // Found header SavegameHeader saveHeader; if (!readSavegameHeader(readStream, saveHeader)) { delete readStream; return false; } if (header) { *header = saveHeader; delete readStream; return true; } g_engine->setTotalPlayTime(saveHeader.playTime * 1000); } // Get in the savegame syncGame(readStream, nullptr, tiny); delete readStream; return true; } /** * Returns true if a given savegame exists */ bool CGEEngine::savegameExists(int slotNumber) { Common::String slotName = getSaveStateName(slotNumber); Common::InSaveFile *saveFile = g_system->getSavefileManager()->openForLoading(slotName); bool result = saveFile != nullptr; delete saveFile; return result; } Common::Error CGEEngine::loadGameState(int slot) { // Clear current game activity sceneDown(); _hero->park(); resetGame(); // If music is playing, kill it. if (_music) _midiPlayer->killMidi(); // Load the game loadGame(slot, nullptr); _commandHandler->addCommand(kCmdLevel, -1, _oldLev, &_sceneLight); _sceneLight->gotoxy(kSceneX + ((_now - 1) % kSceneNx) * kSceneDx + kSceneSX, kSceneY + ((_now - 1) / kSceneNx) * kSceneDy + kSceneSY); sceneUp(); return Common::kNoError; } void CGEEngine::resetGame() { _vga->_spareQ->clear(); _commandHandler->reset(); } Common::Error CGEEngine::saveGameState(int slot, const Common::String &desc, bool isAutosave) { sceneDown(); _hero->park(); _oldLev = _lev; int x = _hero->_x; int y = _hero->_y; int z = _hero->_z; // Write out the user's progress saveGame(slot, desc); _commandHandler->addCommand(kCmdLevel, -1, _oldLev, &_sceneLight); // Reload the scene sceneUp(); // Restore player position _hero->gotoxy(x, y); _hero->_z = z; return Common::kNoError; } void CGEEngine::saveGame(int slotNumber, const Common::String &desc) { // Set up the serializer Common::String slotName = getSaveStateName(slotNumber); Common::OutSaveFile *saveFile = g_system->getSavefileManager()->openForSaving(slotName); // Write out the ScummVM savegame header SavegameHeader header; header.saveName = desc; header.version = kSavegameVersion; writeSavegameHeader(saveFile, header); // Write out the data of the savegame syncGame(nullptr, saveFile, false); // Finish writing out game data saveFile->finalize(); delete saveFile; } void CGEEngine::writeSavegameHeader(Common::OutSaveFile *out, SavegameHeader &header) { // Write out a savegame header out->write(savegameStr, kSavegameStrSize + 1); out->writeByte(kSavegameVersion); // Write savegame name out->write(header.saveName.c_str(), header.saveName.size() + 1); // Get the active palette uint8 thumbPalette[256 * 3]; g_system->getPaletteManager()->grabPalette(thumbPalette, 0, 256); // Create a thumbnail and save it Graphics::Surface *thumb = new Graphics::Surface(); Graphics::Surface *s = _vga->_page[0]; ::createThumbnail(thumb, (const byte *)s->getPixels(), kScrWidth, kScrHeight, thumbPalette); Graphics::saveThumbnail(*out, *thumb); thumb->free(); delete thumb; // Write out the save date/time TimeDate td; g_system->getTimeAndDate(td); out->writeSint16LE(td.tm_year + 1900); out->writeSint16LE(td.tm_mon + 1); out->writeSint16LE(td.tm_mday); out->writeSint16LE(td.tm_hour); out->writeSint16LE(td.tm_min); out->writeUint32LE(g_engine->getTotalPlayTime() / 1000); } void CGEEngine::syncGame(Common::SeekableReadStream *readStream, Common::WriteStream *writeStream, bool tiny) { Common::Serializer s(readStream, writeStream); if (s.isSaving()) { for (int i = 0; i < kPocketNX; i++) { Sprite *pocSpr = _pocket[i]; _pocref[i] = (pocSpr) ? pocSpr->_ref : -1; } // Skip Digital and Midi volumes, useless under ScummVM _volume[0] = 0; _volume[1] = 0; } // Synchronise header data syncHeader(s); if (s.isSaving()) { // Loop through saving the sprite data for (Sprite *spr = _vga->_spareQ->first(); spr; spr = spr->_next) { if (!s.err()) spr->sync(s); } } else { // Loading game if (_mode == 0) { // Skip Digital and Midi volumes, useless under ScummVM sndSetVolume(); } if (!tiny) { // load sprites & pocket while (readStream->pos() < readStream->size()) { Sprite S(this, nullptr); S.sync(s); S._prev = S._next = nullptr; Sprite *spr = (scumm_stricmp(S._file + 2, "MUCHA") == 0) ? new Fly(this, nullptr) : new Sprite(this, nullptr); assert(spr != nullptr); *spr = S; _vga->_spareQ->append(spr); } for (int i = 0; i < kPocketNX; i++) { int r = _pocref[i]; _pocket[i] = (r < 0) ? nullptr : _vga->_spareQ->locate(r); } } } } WARN_UNUSED_RESULT bool CGEEngine::readSavegameHeader(Common::InSaveFile *in, SavegameHeader &header, bool skipThumbnail) { header.version = 0; header.saveName.clear(); header.thumbnail = nullptr; header.saveYear = 0; header.saveMonth = 0; header.saveDay = 0; header.saveHour = 0; header.saveMinutes = 0; header.playTime = 0; // Get the savegame version header.version = in->readByte(); if (header.version > kSavegameVersion) return false; // Read in the string char ch; while ((ch = (char)in->readByte()) != '\0') header.saveName += ch; // Get the thumbnail if (!Graphics::loadThumbnail(*in, header.thumbnail, skipThumbnail)) { return false; } // Read in save date/time header.saveYear = in->readSint16LE(); header.saveMonth = in->readSint16LE(); header.saveDay = in->readSint16LE(); header.saveHour = in->readSint16LE(); header.saveMinutes = in->readSint16LE(); if (header.version >= 3) { header.playTime = in->readUint32LE(); } return true; } void CGEEngine::heroCover(int cvr) { debugC(1, kCGEDebugEngine, "CGEEngine::heroCover(%d)", cvr); _commandHandler->addCommand(kCmdCover, 1, cvr, nullptr); } void CGEEngine::trouble(int seq, int text) { debugC(1, kCGEDebugEngine, "CGEEngine::trouble(%d, %d)", seq, text); _hero->park(); _commandHandler->addCommand(kCmdWait, -1, -1, _hero); _commandHandler->addCommand(kCmdSeq, -1, seq, _hero); _commandHandler->addCommand(kCmdSound, -1, 2, _hero); _commandHandler->addCommand(kCmdWait, -1, -1, _hero); _commandHandler->addCommand(kCmdSay, 1, text, _hero); } void CGEEngine::offUse() { debugC(1, kCGEDebugEngine, "CGEEngine::offUse()"); trouble(kSeqOffUse, kOffUse + newRandom(_offUseCount)); } void CGEEngine::tooFar() { debugC(1, kCGEDebugEngine, "CGEEngine::tooFar()"); trouble(kSeqTooFar, kTooFar); } void CGEEngine::loadHeroXY() { debugC(1, kCGEDebugEngine, "CGEEngine::loadHeroXY()"); EncryptedStream cf(_resman, "CGE.HXY"); uint16 x, y; for (uint i = 0; i < ARRAYSIZE(_heroXY); i++) { _heroXY[i].x = 0; _heroXY[i].y = 0; } if (!cf.err()) { for (int i = 0; i < kSceneMax; ++i) { cf.read((byte *)&x, 2); cf.read((byte *)&y, 2); _heroXY[i].x = (int16)FROM_LE_16(x); _heroXY[i].y = (int16)FROM_LE_16(y); } } } void CGEEngine::loadMapping() { debugC(1, kCGEDebugEngine, "CGEEngine::loadMapping()"); if (_now <= kSceneMax) { EncryptedStream cf(_resman, "CGE.TAB"); if (!cf.err()) { // Move to the data for the given room cf.seek((_now - 1) * kMapArrSize); // Read in the data for (int z = 0; z < kMapZCnt; ++z) { cf.read(&_clusterMap[z][0], kMapXCnt); } } } } Square::Square(CGEEngine *vm) : Sprite(vm, nullptr), _vm(vm) { _flags._kill = true; _flags._bDel = false; BitmapPtr *MB = new BitmapPtr[2]; MB[0] = new Bitmap(_vm, "BRICK"); MB[1] = nullptr; setShapeList(MB); } void Square::touch(uint16 mask, int x, int y, Common::KeyCode keyCode) { Sprite::touch(mask, x, y, keyCode); if (mask & kMouseLeftUp) { _vm->XZ(_x + x, _y + y).cell() = 0; _vm->_commandHandlerTurbo->addCommand(kCmdKill, -1, 0, this); } } void CGEEngine::setMapBrick(int x, int z) { debugC(1, kCGEDebugEngine, "CGEEngine::setMapBrick(%d, %d)", x, z); Square *s = new Square(this); char n[6]; s->gotoxy(x * kMapGridX, kMapTop + z * kMapGridZ); Common::sprintf_s(n, "%02d:%02d", x, z); _clusterMap[z][x] = 1; s->setName(n); _vga->_showQ->insert(s, _vga->_showQ->first()); } void CGEEngine::keyClick() { debugC(1, kCGEDebugEngine, "CGEEngine::keyClick()"); _commandHandlerTurbo->addCommand(kCmdSound, -1, 5, nullptr); } void CGEEngine::resetQSwitch() { debugC(1, kCGEDebugEngine, "CGEEngine::resetQSwitch()"); _commandHandlerTurbo->addCommand(kCmdSeq, 123, 0, nullptr); keyClick(); } void CGEEngine::quit() { debugC(1, kCGEDebugEngine, "CGEEngine::quit()"); static Choice QuitMenu[] = { { nullptr, &CGEEngine::startCountDown }, { nullptr, &CGEEngine::resetQSwitch }, { nullptr, &CGEEngine::dummy } }; if (_commandHandler->idle() && !_hero->_flags._hide) { if (Vmenu::_addr) { _commandHandlerTurbo->addCommand(kCmdKill, -1, 0, Vmenu::_addr); resetQSwitch(); } else { QuitMenu[0]._text = _text->getText(kQuit); QuitMenu[1]._text = _text->getText(kNoQuit); (new Vmenu(this, QuitMenu, -1, -1))->setName(_text->getText(kQuitTitle)); _commandHandlerTurbo->addCommand(kCmdSeq, 123, 1, nullptr); keyClick(); } } } void CGEEngine::miniStep(int stp) { debugC(1, kCGEDebugEngine, "CGEEngine::miniStep(%d)", stp); if (stp < 0) { _miniScene->_flags._hide = true; } else { *_miniShp[0] = *_miniShpList[stp]; _miniScene->_flags._hide = false; } } void CGEEngine::postMiniStep(int step) { debugC(6, kCGEDebugEngine, "CGEEngine::postMiniStep(%d)", step); if (_miniScene && step != _recentStep) _commandHandlerTurbo->addCallback(kCmdExec, -1, _recentStep = step, kMiniStep); } void CGEEngine::showBak(int ref) { debugC(1, kCGEDebugEngine, "CGEEngine::showBack(%d)", ref); Sprite *spr = _vga->_spareQ->locate(ref); if (!spr) return; _bitmapPalette = _vga->_sysPal; spr->expand(); _bitmapPalette = nullptr; spr->show(2); _vga->copyPage(1, 2); _sys->setPal(); spr->contract(); } void CGEEngine::sceneUp() { debugC(1, kCGEDebugEngine, "CGEEngine::sceneUp()"); const int BakRef = 1000 * _now; if (_music) _midiPlayer->loadMidi(_now); showBak(BakRef); loadMapping(); Sprite *spr = _vga->_spareQ->first(); while (spr) { Sprite *n = spr->_next; if (spr->_scene == _now || spr->_scene == 0) if (spr->_ref != BakRef) { if (spr->_flags._back) spr->backShow(); else expandSprite(spr); } spr = n; } _sound->stop(); _fx->clear(); _fx->preload(0); _fx->preload(BakRef); if (_hero) { _hero->gotoxy(_heroXY[_now - 1].x, _heroXY[_now - 1].y); // following 2 lines trims Hero's Z position! _hero->tick(); _hero->_time = 1; _hero->_flags._hide = false; } if (!_dark) _vga->sunset(); _vga->copyPage(0, 1); selectPocket(-1); if (_hero) { _vga->_showQ->insert(_vga->_showQ->remove(_hero)); if (_shadow) { _vga->_showQ->remove(_shadow); _shadow->makeXlat(_vga->glass(_vga->_sysPal, 204, 204, 204)); _vga->_showQ->insert(_shadow, _hero); _shadow->_z = _hero->_z; } } feedSnail(_vga->_showQ->locate(BakRef + 999), kTake); _vga->show(); _vga->copyPage(1, 0); _vga->show(); _vga->sunrise(_vga->_sysPal); _dark = false; if (!_startupMode) _mouse->on(); } void CGEEngine::sceneDown() { debugC(1, kCGEDebugEngine, "CGEEngine::sceneDown()"); if (_horzLine && !_horzLine->_flags._hide) switchMapping(); for (Sprite *spr = _vga->_showQ->first(); spr;) { Sprite *n = spr->_next; if (spr->_ref >= 1000 /*&& spr->_scene*/) { if (spr->_ref % 1000 == 999) feedSnail(spr, kTake); _vga->_spareQ->append(_vga->_showQ->remove(spr)); } spr = n; } } void CGEEngine::xScene() { debugC(6, kCGEDebugEngine, "CGEEngine::xScene()"); sceneDown(); if (_lev != -1) _commandHandler->addCommand(kCmdLevel, -1, _lev, &_sceneLight); sceneUp(); } void CGEEngine::qGame() { debugC(1, kCGEDebugEngine, "CGEEngine::qGame()"); sceneDown(); _hero->park(); _oldLev = _lev; // Write out the user's progress saveGame(0, Common::String("Automatic Savegame")); _vga->sunset(); _endGame = true; } void CGEEngine::switchScene(int newScene) { debugC(1, kCGEDebugEngine, "CGEEngine::switchScene(%d)", newScene); if (newScene == _now) return; if (newScene < 0) { _commandHandler->addCommand(kCmdLabel, -1, 0, nullptr); // wait for repaint _commandHandler->addCallback(kCmdExec, -1, 0, kQGame); // quit game } else { _now = newScene; _mouse->off(); if (_hero) { _hero->park(); _hero->step(0); _vga->_spareQ->_show = false; } _sceneLight->gotoxy(kSceneX + ((_now - 1) % kSceneNx) * kSceneDx + kSceneSX, kSceneY + ((_now - 1) / kSceneNx) * kSceneDy + kSceneSY); killText(); if (!_startupMode) keyClick(); _commandHandler->addCommand(kCmdLabel, -1, 0, nullptr); // wait for repaint _commandHandler->addCallback(kCmdExec, 0, 0, kXScene); // switch scene } } System::System(CGEEngine *vm) : Sprite(vm, nullptr), _vm(vm) { _funDel = kHeroFun0; setPal(); tick(); } void System::setPal() { Dac *p = _vm->_vga->_sysPal + 256 - ARRAYSIZE(g_stdPal); for (uint i = 0; i < ARRAYSIZE(g_stdPal); i++) { p[i]._r = g_stdPal[i]._r >> 2; p[i]._g = g_stdPal[i]._g >> 2; p[i]._b = g_stdPal[i]._b >> 2; } } void System::funTouch() { uint16 n = (_vm->_flag[0]) ? kHeroFun1 : kHeroFun0; // PAIN flag if (_vm->_talk == nullptr || n > _funDel) _funDel = n; } void System::touch(uint16 mask, int x, int y, Common::KeyCode keyCode) { funTouch(); if (mask & kEventKeyb) { if (keyCode == Common::KEYCODE_ESCAPE) { // The original was calling keyClick() // The sound is uselessly annoying and noisy, so it has been removed _vm->killText(); if (_vm->_startupMode == 1) { _vm->_commandHandler->addCommand(kCmdClear, -1, 0, nullptr); return; } } } else { if (_vm->_startupMode) return; int selectedScene = 0; _vm->_infoLine->update(nullptr); if (y >= kWorldHeight ) { if (x < kButtonX) { // select scene? if (y >= kSceneY && y < kSceneY + kSceneNy * kSceneDy && x >= kSceneX && x < kSceneX + kSceneNx * kSceneDx && !_vm->_game) { selectedScene = ((y - kSceneY) / kSceneDy) * kSceneNx + (x - kSceneX) / kSceneDx + 1; if (selectedScene > _vm->_maxScene) selectedScene = 0; } else { selectedScene = 0; } } else if (mask & kMouseLeftUp) { if (y >= kPocketY && y < kPocketY + kPocketNY * kPocketDY && x >= kPocketX && x < kPocketX + kPocketNX * kPocketDX) { int n = ((y - kPocketY) / kPocketDY) * kPocketNX + (x - kPocketX) / kPocketDX; _vm->selectPocket(n); } } } _vm->postMiniStep(selectedScene - 1); if (mask & kMouseLeftUp) { if (selectedScene && _vm->_commandHandler->idle() && _vm->_hero->_tracePtr < 0) _vm->switchScene(selectedScene); if (_vm->_horzLine && !_vm->_horzLine->_flags._hide) { if (y >= kMapTop && y < kMapTop + kMapHig) { Cluster tmpCluster = _vm->XZ(x, y); int16 x1 = tmpCluster._pt.x; int16 z1 = tmpCluster._pt.y; _vm->_clusterMap[z1][x1] = 1; _vm->setMapBrick(x1, z1); } } else { if (!_vm->_talk && _vm->_commandHandler->idle() && _vm->_hero && y >= kMapTop && y < kMapTop + kMapHig && !_vm->_game) { _vm->_hero->findWay(_vm->XZ(x, y)); } } } } } void System::tick() { if (!_vm->_startupMode) if (--_funDel == 0) { _vm->killText(); if (_vm->_commandHandler->idle()) { if (_vm->_flag[0]) // Pain flag _vm->heroCover(9); else { int n = _vm->newRandom(100); if (n > 96) _vm->heroCover(6 + (_vm->_hero->_x + _vm->_hero->_w / 2 < kScrWidth / 2)); else if (n > 90) _vm->heroCover(5); else if (n > 60) _vm->heroCover(4); else _vm->heroCover(3); } } funTouch(); } _time = kSystemRate; } /** * Switch greyscale mode on/off */ void CGEEngine::switchColorMode() { debugC(1, kCGEDebugEngine, "CGEEngine::switchColorMode()"); _commandHandlerTurbo->addCommand(kCmdSeq, 121, _vga->_mono = !_vga->_mono, nullptr); keyClick(); _vga->setColors(_vga->_sysPal, 64); } /** * Switch music on/off */ void CGEEngine::switchMusic() { debugC(1, kCGEDebugEngine, "CGEEngine::switchMusic()"); _commandHandlerTurbo->addCommand(kCmdSeq, 122, (_music = !_music), nullptr); keyClick(); if (_music) _midiPlayer->loadMidi(_now); else _midiPlayer->killMidi(); } /** * Shutdown game */ void CGEEngine::startCountDown() { debugC(1, kCGEDebugEngine, "CGEEngine::startCountDown()"); switchScene(-1); } void CGEEngine::switchMapping() { assert(_horzLine); debugC(1, kCGEDebugEngine, "CGEEngine::switchMapping()"); if (_horzLine && _horzLine->_flags._hide) { for (int i = 0; i < kMapZCnt; i++) { for (int j = 0; j < kMapXCnt; j++) { if (_clusterMap[i][j]) setMapBrick(j, i); } } } else { for (Sprite *s = _vga->_showQ->first(); s; s = s->_next) if (s->_w == kMapGridX && s->_h == kMapGridZ) _commandHandlerTurbo->addCommand(kCmdKill, -1, 0, s); } _horzLine->_flags._hide = !_horzLine->_flags._hide; } void CGEEngine::killSprite() { debugC(1, kCGEDebugEngine, "CGEEngine::killSprite()"); _sprite->_flags._kill = true; _sprite->_flags._bDel = true; _commandHandlerTurbo->addCommand(kCmdKill, -1, 0, _sprite); _sprite = nullptr; } void CGEEngine::optionTouch(int opt, uint16 mask) { switch (opt) { case 1: if (mask & kMouseLeftUp) switchColorMode(); break; case 2: if (mask & kMouseLeftUp) switchMusic(); else if (mask & kMouseRightUp) openMainMenuDialog(); break; case 3: if (mask & kMouseLeftUp) quit(); break; default: break; } } #pragma argsused void Sprite::touch(uint16 mask, int x, int y, Common::KeyCode keyCode) { _vm->_sys->funTouch(); if ((mask & kEventAttn) != 0) return; _vm->_infoLine->update(name()); if (mask & (kMouseRightDown | kMouseLeftDown)) _vm->_sprite = this; if (_ref / 10 == 12) { _vm->optionTouch(_ref % 10, mask); return; } if (_flags._syst) return; // cannot access system sprites if (_vm->_game) if (mask & kMouseLeftUp) { mask &= ~kMouseLeftUp; mask |= kMouseRightUp; } if ((mask & kMouseRightUp) && _vm->_commandHandler->idle()) { Sprite *ps = (_vm->_pocLight->_seqPtr) ? _vm->_pocket[_vm->_pocPtr] : nullptr; if (ps) { if (_flags._kept || _vm->_hero->distance(this) < kDistMax) { if (works(ps)) { _vm->feedSnail(ps, kTake); } else _vm->offUse(); _vm->selectPocket(-1); } else _vm->tooFar(); } else { if (_flags._kept) { mask |= kMouseLeftUp; } else { if (_vm->_hero->distance(this) < kDistMax) { if (_flags._port) { if (_vm->findPocket(nullptr) < 0) { _vm->pocFul(); } else { _vm->_commandHandler->addCommand(kCmdReach, -1, -1, this); _vm->_commandHandler->addCommand(kCmdKeep, -1, -1, this); _flags._port = false; } } else { if (_takePtr != kNoPtr) { if (snList(kTake)[_takePtr]._commandType == kCmdNext) _vm->offUse(); else _vm->feedSnail(this, kTake); } else { _vm->offUse(); } } } else { _vm->tooFar(); } } } } if ((mask & kMouseLeftUp) && _vm->_commandHandler->idle()) { if (_flags._kept) { for (int n = 0; n < kPocketNX; n++) { if (_vm->_pocket[n] == this) { _vm->selectPocket(n); break; } } } else { _vm->_commandHandler->addCommand(kCmdWalk, -1, -1, this); // Hero->FindWay(this); } } } void CGEEngine::loadSprite(const char *fname, int ref, int scene, int col = 0, int row = 0, int pos = 0) { static const char *Comd[] = { "Name", "Type", "Phase", "East", "Left", "Right", "Top", "Bottom", "Seq", "Near", "Take", "Portable", "Transparent", nullptr }; static const char *Type[] = { "DEAD", "AUTO", "WALK", "NEWTON", "LISSAJOUS", "FLY", nullptr }; int shpcnt = 0; int type = 0; // DEAD bool east = false; bool port = false; bool tran = false; char tmpStr[kLineMax + 1]; Common::String line; STATIC_ASSERT(kLineMax + 1 >= kPathMax, mergeExt_expects_kPathMax_buffer); mergeExt(tmpStr, fname, kSprExt); if (_resman->exist(tmpStr)) { // sprite description file exist EncryptedStream sprf(_resman, tmpStr); if (sprf.err()) error("Bad SPR [%s]", tmpStr); uint16 len; int i, lcnt = 0; for (line = sprf.readLine(); !sprf.eos(); line = sprf.readLine()) { len = line.size(); lcnt++; Common::strlcpy(tmpStr, line.c_str(), sizeof(tmpStr)); if (len == 0 || *tmpStr == '.') continue; if ((i = takeEnum(Comd, strtok(tmpStr, " =\t"))) < 0) error("Bad line %d [%s]", lcnt, fname); switch (i) { default: case 0: // Name - will be taken in Expand routine break; case 1: // Type if ((type = takeEnum(Type, strtok(nullptr, " \t,;/"))) < 0) error("Bad line %d [%s]", lcnt, fname); break; case 2: // Phase shpcnt++; break; case 3: // East east = (atoi(strtok(nullptr, " \t,;/")) != 0); break; case 11: // Portable port = (atoi(strtok(nullptr, " \t,;/")) != 0); break; case 12: // Transparent tran = (atoi(strtok(nullptr, " \t,;/")) != 0); break; } } if (! shpcnt) error("No shapes [%s]", fname); } else { // no sprite description: mono-shaped sprite with only .BMP file ++shpcnt; } // make sprite of choosen type switch (type) { case 1: // AUTO _sprite = new Sprite(this, nullptr); if (_sprite) { _sprite->gotoxy(col, row); } break; case 2: { // WALK Walk *w = new Walk(this, nullptr); if (w && ref == 1) { w->gotoxy(col, row); if (_hero) error("2nd HERO [%s]", fname); _hero = w; } _sprite = w; break; } case 3: // NEWTON case 4: // LISSAJOUS error("Bad type [%s]", fname); break; case 5: { // FLY Fly *f = new Fly(this, nullptr); _sprite = f; break; } default: // DEAD _sprite = new Sprite(this, nullptr); if (_sprite) _sprite->gotoxy(col, row); break; } if (_sprite) { _sprite->_ref = ref; _sprite->_scene = scene; _sprite->_z = pos; _sprite->_flags._east = east; _sprite->_flags._port = port; _sprite->_flags._tran = tran; _sprite->_flags._kill = true; _sprite->_flags._bDel = true; // Extract the filename, without the extension Common::strlcpy(_sprite->_file, fname, sizeof(_sprite->_file)); char *p = strchr(_sprite->_file, '.'); if (p) *p = '\0'; _sprite->_shpCnt = shpcnt; _vga->_spareQ->append(_sprite); } } void CGEEngine::loadScript(const char *fname) { EncryptedStream scrf(_resman, fname); if (scrf.err()) return; bool ok = true; int lcnt = 0; char tmpStr[kLineMax+1]; Common::String line; for (line = scrf.readLine(); !scrf.eos(); line = scrf.readLine()) { char *p; lcnt++; Common::strlcpy(tmpStr, line.c_str(), sizeof(tmpStr)); if ((line.size() == 0) || (*tmpStr == '.')) continue; ok = false; // not OK if break // sprite ident number if ((p = strtok(tmpStr, " \t\n")) == nullptr) break; int SpI = atoi(p); // sprite file name char *SpN; if ((SpN = strtok(nullptr, " ,;/\t\n")) == nullptr) break; // sprite scene if ((p = strtok(nullptr, " ,;/\t\n")) == nullptr) break; int SpA = atoi(p); // sprite column if ((p = strtok(nullptr, " ,;/\t\n")) == nullptr) break; int SpX = atoi(p); // sprite row if ((p = strtok(nullptr, " ,;/\t\n")) == nullptr) break; int SpY = atoi(p); // sprite Z pos if ((p = strtok(nullptr, " ,;/\t\n")) == nullptr) break; int SpZ = atoi(p); // sprite life if ((p = strtok(nullptr, " ,;/\t\n")) == nullptr) break; bool BkG = atoi(p) == 0; ok = true; // no break: OK _sprite = nullptr; loadSprite(SpN, SpI, SpA, SpX, SpY, SpZ); if (_sprite && BkG) _sprite->_flags._back = true; } if (!ok) error("Bad INI line %d [%s]", lcnt, fname); } Sprite *CGEEngine::locate(int ref) { Sprite *spr = _vga->_showQ->locate(ref); return (spr) ? spr : _vga->_spareQ->locate(ref); } Sprite *CGEEngine::spriteAt(int x, int y) { Sprite *spr = nullptr, * tail = _vga->_showQ->last(); if (tail) { for (spr = tail->_prev; spr; spr = spr->_prev) { if (! spr->_flags._hide && ! spr->_flags._tran) { if (spr->shp()->solidAt(x - spr->_x, y - spr->_y)) break; } } } return spr; } Cluster CGEEngine::XZ(int16 x, int16 y) { if (y < kMapTop) y = kMapTop; if (y > kMapTop + kMapHig - kMapGridZ) y = kMapTop + kMapHig - kMapGridZ; return Cluster(this, x / kMapGridX, (y - kMapTop) / kMapGridZ); } void CGEEngine::killText() { if (!_talk) return; _commandHandlerTurbo->addCommand(kCmdKill, -1, 0, _talk); _talk = nullptr; } void CGEEngine::mainLoop() { _vga->show(); _commandHandlerTurbo->runCommand(); _commandHandler->runCommand(); // Handle a delay between game frames handleFrame(); // Handle any pending events _eventManager->poll(); // Check shouldQuit() _quitFlag = shouldQuit(); } void CGEEngine::handleFrame() { // Game frame delay uint32 millis = g_system->getMillis(); while (!_quitFlag && (millis < (_lastFrame + kGameFrameDelay))) { // Handle any pending events _eventManager->poll(); if (millis >= (_lastTick + kGameTickDelay)) { // Dispatch the tick to any active objects tick(); _lastTick = millis; } // Slight delay g_system->delayMillis(5); millis = g_system->getMillis(); } _lastFrame = millis; if (millis >= (_lastTick + kGameTickDelay)) { // Dispatch the tick to any active objects tick(); _lastTick = millis; } } void CGEEngine::tick() { for (Sprite *spr = _vga->_showQ->first(); spr; spr = spr->_next) { if (spr->_time) { if (!spr->_flags._hide) { if (--spr->_time == 0) spr->tick(); } } } } void CGEEngine::loadUser() { // set scene if (_mode == 0) { // user .SVG file found - load it from slot 0 loadGame(0, nullptr); } else if (_mode == 1) { // Load either initial game state savegame or launcher specified savegame loadGame(_startGameSlot, nullptr); } else { error("Creating setup savegames not supported"); } loadScript("CGE.IN0"); } void CGEEngine::runGame() { if (_quitFlag) return; loadHeroXY(); _sceneLight->_flags._tran = true; _vga->_showQ->append(_sceneLight); _sceneLight->_flags._hide = false; const Seq pocSeq[] = { { 0, 0, 0, 0, 20 }, { 1, 2, 0, 0, 4 }, { 2, 3, 0, 0, 4 }, { 3, 4, 0, 0, 16 }, { 2, 5, 0, 0, 4 }, { 1, 6, 0, 0, 4 }, { 0, 1, 0, 0, 16 }, }; Seq *seq = (Seq *)malloc(7 * sizeof(Seq)); Common::copy(pocSeq, pocSeq + 7, seq); _pocLight->setSeq(seq); _pocLight->_flags._tran = true; _pocLight->_time = 1; _pocLight->_z = 120; _vga->_showQ->append(_pocLight); selectPocket(-1); _vga->_showQ->append(_mouse); loadUser(); if ((_sprite = _vga->_spareQ->locate(121)) != nullptr) _commandHandlerTurbo->addCommand(kCmdSeq, -1, _vga->_mono, _sprite); if ((_sprite = _vga->_spareQ->locate(122)) != nullptr) _sprite->step(_music); _commandHandlerTurbo->addCommand(kCmdSeq, -1, _music, _sprite); if (!_music) _midiPlayer->killMidi(); if (_resman->exist("MINI.SPR")) { _miniShp = new BitmapPtr[2]; _miniShp[0] = _miniShp[1] = nullptr; loadSprite("MINI", -1, 0, kMiniX, kMiniY); expandSprite(_miniScene = _sprite); // NULL is ok if (_miniScene) { _miniScene->_flags._kill = false; _miniScene->_flags._hide = true; _miniShp[0] = new Bitmap(this, *_miniScene->shp()); _miniShpList = _miniScene->setShapeList(_miniShp); postMiniStep(-1); } } if (_hero) { expandSprite(_hero); _hero->gotoxy(_heroXY[_now - 1].x, _heroXY[_now - 1].y); if (_resman->exist("00SHADOW.SPR")) { loadSprite("00SHADOW", -1, 0, _hero->_x + 14, _hero->_y + 51); delete _shadow; if ((_shadow = _sprite) != nullptr) { _shadow->_ref = 2; _shadow->_flags._tran = true; _shadow->_flags._kill = false; _hero->_flags._shad = true; _vga->_showQ->insert(_vga->_spareQ->remove(_shadow), _hero); } } } _infoLine->gotoxy(kInfoX, kInfoY); _infoLine->_flags._tran = true; _infoLine->update(nullptr); _vga->_showQ->insert(_infoLine); _debugLine->_z = 126; _vga->_showQ->insert(_debugLine); if (_horzLine) { _horzLine->_y = kMapTop - (kMapTop > 0); _horzLine->_z = 126; _vga->_showQ->insert(_horzLine); } _mouse->_busy = _vga->_spareQ->locate(kBusyRef); if (_mouse->_busy) expandSprite(_mouse->_busy); _startupMode = 0; _commandHandler->addCommand(kCmdLevel, -1, _oldLev, &_sceneLight); _sceneLight->gotoxy(kSceneX + ((_now - 1) % kSceneNx) * kSceneDx + kSceneSX, kSceneY + ((_now - 1) / kSceneNx) * kSceneDy + kSceneSY); sceneUp(); _keyboard->setClient(_sys); // main loop while (!_endGame && !_quitFlag) { if (_flag[3]) // Flag FINIS _commandHandler->addCallback(kCmdExec, -1, 0, kQGame); mainLoop(); } // If finishing game due to closing ScummVM window, explicitly save the game if (!_endGame && canSaveGameStateCurrently()) qGame(); _keyboard->setClient(nullptr); _commandHandler->addCommand(kCmdClear, -1, 0, nullptr); _commandHandlerTurbo->addCommand(kCmdClear, -1, 0, nullptr); _mouse->off(); _vga->_showQ->clear(); _vga->_spareQ->clear(); _hero = nullptr; _shadow = nullptr; } void CGEEngine::movie(const char *ext) { assert(ext); if (_quitFlag) return; char fn[12]; Common::sprintf_s(fn, "CGE.%s", (*ext == '.') ? ext +1 : ext); if (_resman->exist(fn)) { loadScript(fn); expandSprite(_vga->_spareQ->locate(999)); feedSnail(_vga->_showQ->locate(999), kTake); _vga->_showQ->append(_mouse); _keyboard->setClient(_sys); while (!_commandHandler->idle() && !_quitFlag) mainLoop(); _keyboard->setClient(nullptr); _commandHandler->addCommand(kCmdClear, -1, 0, nullptr); _commandHandlerTurbo->addCommand(kCmdClear, -1, 0, nullptr); _vga->_showQ->clear(); _vga->_spareQ->clear(); } } bool CGEEngine::showTitle(const char *name) { if (_quitFlag) return false; _bitmapPalette = _vga->_sysPal; BitmapPtr *LB = new BitmapPtr[2]; LB[0] = new Bitmap(this, name); LB[1] = nullptr; _bitmapPalette = nullptr; Sprite D(this, LB); D._flags._kill = true; D._flags._bDel = true; D.center(); D.show(2); if (_mode == 2) { inf(kSavegame0Name); _talk->show(2); } _vga->sunset(); _vga->copyPage(1, 2); _vga->copyPage(0, 1); selectPocket(-1); _vga->sunrise(_vga->_sysPal); if (_mode < 2) { // At this point the game originally set the protection variables // used by the copy protection check movie(kPaylistExt); // paylist _vga->copyPage(1, 2); _vga->copyPage(0, 1); _vga->_showQ->append(_mouse); // In the original game, the user had to enter his name // As it was only used to name savegames, it has been removed _vga->_showQ->clear(); _vga->copyPage(0, 2); // The original was automatically loading the savegame when available if (_mode == 0) _mode++; } if (_mode < 2) movie(kWinkExt); _vga->copyPage(0, 2); return true; } void CGEEngine::cge_main() { memset(_barriers, 0xFF, sizeof(_barriers)); if (!_mouse->_exist) error("%s", _text->getText(kTextNoMouse)); if (!_resman->exist(kSavegame0Name)) _mode = 2; _debugLine->_flags._hide = true; if (_horzLine) _horzLine->_flags._hide = true; if (_music) _midiPlayer->loadMidi(0); if (_startGameSlot != -1) { // Starting up a savegame from the launcher _mode++; runGame(); _startupMode = 2; if (_flag[3]) // Flag FINIS movie(kEndgExt); } else { if (_mode < 2) movie(kLgoExt); if (showTitle("WELCOME")) { if (_mode == 1) movie(kIntroExt); runGame(); _startupMode = 2; if (_flag[3]) // Flag FINIS movie(kEndgExt); } else _vga->sunset(); } } } // End of namespace CGE