/* 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. * */ /* * This code is based on the original source code of Lord Avalot d'Argent version 1.3. * Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman. */ #include "avalanche/avalanche.h" #include "common/random.h" #include "common/savefile.h" #include "graphics/thumbnail.h" namespace Avalanche { AvalancheEngine::AvalancheEngine(OSystem *syst, const AvalancheGameDescription *gd) : Engine(syst), _gameDescription(gd), _fxHidden(false), _interrogation(0) { _system = syst; _console = new AvalancheConsole(this); _rnd = new Common::RandomSource("avalanche"); TimeDate time; _system->getTimeAndDate(time); _rnd->setSeed(time.tm_sec + time.tm_min + time.tm_hour); _showDebugLines = false; _clock = nullptr; _graphics = nullptr; _parser = nullptr; _pingo = nullptr; _dialogs = nullptr; _background = nullptr; _sequence = nullptr; _timer = nullptr; _animation = nullptr; _menu = nullptr; _closing = nullptr; _sound = nullptr; _platform = gd->desc.platform; initVariables(); } AvalancheEngine::~AvalancheEngine() { delete _console; delete _rnd; delete _graphics; delete _parser; delete _clock; delete _pingo; delete _dialogs; delete _background; delete _sequence; delete _timer; delete _animation; delete _menu; delete _closing; delete _sound; for (int i = 0; i < 31; i++) { for (int j = 0; j < 2; j++) { if (_also[i][j] != nullptr) { delete _also[i][j]; _also[i][j] = nullptr; } } } } void AvalancheEngine::initVariables() { for (int i = 0; i < 31; i++) { _also[i][0] = nullptr; _also[i][1] = nullptr; } memset(_fxPal, 0, 16 * 16 * 3); for (int i = 0; i < 15; i++) { _peds[i]._direction = kDirNone; _peds[i]._x = 0; _peds[i]._y = 0; _magics[i]._operation = kMagicNothing; _magics[i]._data = 0; } for (int i = 0; i < 7; i++) { _portals[i]._operation = kMagicNothing; _portals[i]._data = 0; } for (int i = 0; i < 30; i++) { _fields[i]._x1 = 0; _fields[i]._y1 = 0; _fields[i]._x2 = 0; _fields[i]._y2 = 0; } _fieldNum = 0; _cp = 0; _ledStatus = 177; _alive = false; _subjectNum = 0; _him = kPeoplePardon; _her = kPeoplePardon; _it = Parser::kPardon; _roomCycles = 0; _doingSpriteRun = false; _isLoaded = false; _soundFx = true; _holdTheDawn = false; _lineNum = 0; for (int i = 0; i < 50; i++) _lines[i]._color = kColorWhite; _dropsOk = false; _cheat = false; _letMeOut = false; _thinks = 2; _thinkThing = true; _seeScroll = false; _currentMouse = 177; _holdLeftMouse = false; _jumpStatus = 0; _mushroomGrowing = false; _crapulusWillTell = false; _enterCatacombsFromLustiesRoom = false; _teetotal = false; _malagauche = 0; _drinking = '\0'; _enteredLustiesRoomAsMonk = false; _catacombX = 0; _catacombY = 0; _avvysInTheCupboard = false; _geidaFollows = false; _givenPotionToGeida = false; _lustieIsAsleep = false; _beenTiedUp = false; _sittingInPub = false; _spurgeTalkCount = 0; _metAvaroid = false; _takenMushroom = false; _givenPenToAyles = false; _askedDogfoodAboutNim = false; _ableToAddTimer = false; _spludwickAtHome = false; _passedCwytalotInHerts = false; _lastRoom = _lastRoomNotMap = kRoomDummy; } Common::ErrorCode AvalancheEngine::initialize() { _graphics = new GraphicManager(this); _parser = new Parser(this); _clock = new Clock(this); _pingo = new Pingo(this); _dialogs = new Dialogs(this); _background = new Background(this); _sequence = new Sequence(this); _timer = new Timer(this); _animation = new Animation(this); _menu = new Menu(this); _closing = new Closing(this); _sound = new SoundHandler(this); _graphics->init(); _dialogs->init(); init(); _parser->init(); return Common::kNoError; } GUI::Debugger *AvalancheEngine::getDebugger() { return _console; } Common::Platform AvalancheEngine::getPlatform() const { return _platform; } bool AvalancheEngine::hasFeature(EngineFeature f) const { return (f == kSupportsSavingDuringRuntime) || (f == kSupportsLoadingDuringRuntime); } const char *AvalancheEngine::getCopyrightString() const { return "Copyright (c) 1994-1995 Mike, Mark and Thomas Thurman."; } void AvalancheEngine::synchronize(Common::Serializer &sz) { _animation->synchronize(sz); _parser->synchronize(sz); _sequence->synchronize(sz); _background->synchronize(sz); sz.syncAsByte(_carryNum); for (int i = 0; i < kObjectNum; i++) sz.syncAsByte(_objects[i]); sz.syncAsSint16LE(_dnascore); sz.syncAsSint32LE(_money); sz.syncAsByte(_room); if (sz.isSaving()) _saveNum++; sz.syncAsByte(_saveNum); sz.syncBytes(_roomCount, 100); sz.syncAsByte(_wonNim); sz.syncAsByte(_wineState); sz.syncAsByte(_cwytalotGone); sz.syncAsByte(_passwordNum); sz.syncAsByte(_aylesIsAwake); sz.syncAsByte(_drawbridgeOpen); sz.syncAsByte(_avariciusTalk); sz.syncAsByte(_rottenOnion); sz.syncAsByte(_onionInVinegar); sz.syncAsByte(_givenToSpludwick); sz.syncAsByte(_brummieStairs); sz.syncAsByte(_cardiffQuestionNum); sz.syncAsByte(_passedCwytalotInHerts); sz.syncAsByte(_avvyIsAwake); sz.syncAsByte(_avvyInBed); sz.syncAsByte(_userMovesAvvy); sz.syncAsByte(_npcFacing); sz.syncAsByte(_givenBadgeToIby); sz.syncAsByte(_friarWillTieYouUp); sz.syncAsByte(_tiedUp); sz.syncAsByte(_boxContent); sz.syncAsByte(_talkedToCrapulus); sz.syncAsByte(_jacquesState); sz.syncAsByte(_bellsAreRinging); sz.syncAsByte(_standingOnDais); sz.syncAsByte(_takenPen); sz.syncAsByte(_arrowInTheDoor); if (sz.isSaving()) { uint16 like2drinkSize = _favoriteDrink.size(); sz.syncAsUint16LE(like2drinkSize); for (uint16 i = 0; i < like2drinkSize; i++) { char actChr = _favoriteDrink[i]; sz.syncAsByte(actChr); } uint16 favoriteSongSize = _favoriteSong.size(); sz.syncAsUint16LE(favoriteSongSize); for (uint16 i = 0; i < favoriteSongSize; i++) { char actChr = _favoriteSong[i]; sz.syncAsByte(actChr); } uint16 worst_place_on_earthSize = _worstPlaceOnEarth.size(); sz.syncAsUint16LE(worst_place_on_earthSize); for (uint16 i = 0; i < worst_place_on_earthSize; i++) { char actChr = _worstPlaceOnEarth[i]; sz.syncAsByte(actChr); } uint16 spare_eveningSize = _spareEvening.size(); sz.syncAsUint16LE(spare_eveningSize); for (uint16 i = 0; i < spare_eveningSize; i++) { char actChr = _spareEvening[i]; sz.syncAsByte(actChr); } } else { if (!_favoriteDrink.empty()) _favoriteDrink.clear(); uint16 like2drinkSize = 0; char actChr = ' '; sz.syncAsUint16LE(like2drinkSize); for (uint16 i = 0; i < like2drinkSize; i++) { sz.syncAsByte(actChr); _favoriteDrink += actChr; } if (!_favoriteSong.empty()) _favoriteSong.clear(); uint16 favourite_songSize = 0; sz.syncAsUint16LE(favourite_songSize); for (uint16 i = 0; i < favourite_songSize; i++) { sz.syncAsByte(actChr); _favoriteSong += actChr; } if (!_worstPlaceOnEarth.empty()) _worstPlaceOnEarth.clear(); uint16 worst_place_on_earthSize = 0; sz.syncAsUint16LE(worst_place_on_earthSize); for (uint16 i = 0; i < worst_place_on_earthSize; i++) { sz.syncAsByte(actChr); _worstPlaceOnEarth += actChr; } if (!_spareEvening.empty()) _spareEvening.clear(); uint16 spare_eveningSize = 0; sz.syncAsUint16LE(spare_eveningSize); for (uint16 i = 0; i < spare_eveningSize; i++) { sz.syncAsByte(actChr); _spareEvening += actChr; } } sz.syncAsSint32LE(_totalTime); sz.syncAsByte(_jumpStatus); sz.syncAsByte(_mushroomGrowing); sz.syncAsByte(_spludwickAtHome); sz.syncAsByte(_lastRoom); sz.syncAsByte(_lastRoomNotMap); sz.syncAsByte(_crapulusWillTell); sz.syncAsByte(_enterCatacombsFromLustiesRoom); sz.syncAsByte(_teetotal); sz.syncAsByte(_malagauche); sz.syncAsByte(_drinking); sz.syncAsByte(_enteredLustiesRoomAsMonk); sz.syncAsByte(_catacombX); sz.syncAsByte(_catacombY); sz.syncAsByte(_avvysInTheCupboard); sz.syncAsByte(_geidaFollows); sz.syncAsByte(_givenPotionToGeida); sz.syncAsByte(_lustieIsAsleep); sz.syncAsByte(_beenTiedUp); sz.syncAsByte(_sittingInPub); sz.syncAsByte(_spurgeTalkCount); sz.syncAsByte(_metAvaroid); sz.syncAsByte(_takenMushroom); sz.syncAsByte(_givenPenToAyles); sz.syncAsByte(_askedDogfoodAboutNim); for (int i = 0; i < 7; i++) { sz.syncAsSint32LE(_timer->_times[i]._timeLeft); sz.syncAsByte(_timer->_times[i]._action); sz.syncAsByte(_timer->_times[i]._reason); } } bool AvalancheEngine::canSaveGameStateCurrently() { // TODO: Refine these!!! return (!_seeScroll && _alive); } Common::Error AvalancheEngine::saveGameState(int slot, const Common::String &desc) { return (saveGame(slot, desc) ? Common::kNoError : Common::kWritingFailed); } bool AvalancheEngine::saveGame(const int16 slot, const Common::String &desc) { Common::String fileName = getSaveFileName(slot); Common::OutSaveFile *f = g_system->getSavefileManager()->openForSaving(fileName); if (!f) { warning("Can't create file '%s', game not saved.", fileName.c_str()); return false; } f->writeUint32LE(MKTAG('A', 'V', 'A', 'L')); // Write version. We can't restore from obsolete versions. f->writeByte(kSavegameVersion); f->writeUint32LE(desc.size()); f->write(desc.c_str(), desc.size()); Graphics::saveThumbnail(*f); TimeDate t; _system->getTimeAndDate(t); f->writeSint16LE(t.tm_mday); f->writeSint16LE(t.tm_mon); f->writeSint16LE(t.tm_year); _totalTime += getTimeInSeconds() - _startTime; Common::Serializer sz(NULL, f); synchronize(sz); f->finalize(); delete f; return true; } Common::String AvalancheEngine::getSaveFileName(const int slot) { return Common::String::format("%s.%03d", _targetName.c_str(), slot); } bool AvalancheEngine::canLoadGameStateCurrently() { // TODO: Refine these!!! return (!_seeScroll); } Common::Error AvalancheEngine::loadGameState(int slot) { return (loadGame(slot) ? Common::kNoError : Common::kReadingFailed); } bool AvalancheEngine::loadGame(const int16 slot) { Common::String fileName = getSaveFileName(slot); Common::InSaveFile *f = g_system->getSavefileManager()->openForLoading(fileName); if (!f) return false; uint32 signature = f->readUint32LE(); if (signature != MKTAG('A', 'V', 'A', 'L')) return false; // Check version. We can't restore from obsolete versions. byte saveVersion = f->readByte(); if (saveVersion != kSavegameVersion) { warning("Savegame of incompatible version!"); delete f; return false; } // Read the description. uint32 descSize = f->readUint32LE(); Common::String description; for (uint32 i = 0; i < descSize; i++) { char actChar = f->readByte(); description += actChar; } description.toUppercase(); Graphics::skipThumbnail(*f); // Read the time the game was saved. TimeDate t; t.tm_mday = f->readSint16LE(); t.tm_mon = f->readSint16LE(); t.tm_year = f->readSint16LE(); resetVariables(); Common::Serializer sz(f, NULL); synchronize(sz); delete f; _isLoaded = true; _ableToAddTimer = false; _seeScroll = true; // This prevents display of the new sprites before the new picture is loaded. if (_holdTheDawn) { _holdTheDawn = false; fadeIn(); } _background->release(); minorRedraw(); _menu->setup(); setRoom(kPeopleAvalot, _room); _alive = true; refreshObjectList(); _animation->updateSpeed(); drawDirection(); _animation->animLink(); _background->update(); Common::String tmpStr = Common::String::format("%cLoaded: %c%s.ASG%c%c%c%s%c%csaved on %s.", kControlItalic, kControlRoman, description.c_str(), kControlCenter, kControlNewLine, kControlNewLine, _roomnName.c_str(), kControlNewLine, kControlNewLine, expandDate(t.tm_mday, t.tm_mon, t.tm_year).c_str()); _dialogs->displayText(tmpStr); AnimationType *avvy = _animation->_sprites[0]; if (avvy->_quick && avvy->_visible) _animation->setMoveSpeed(0, _animation->getDirection()); // We push Avvy in the right direction is he was moving. return true; } Common::String AvalancheEngine::expandDate(int d, int m, int y) { static const char months[12][10] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; Common::String month = Common::String(months[m]); Common::String day = intToStr(d); if (((1 <= d) && (d <= 9)) || ((21 <= d) && (d <= 31))) switch (d % 10) { case 1: day += "st"; break; case 2: day += "nd"; break; case 3: day += "rd"; break; default: day += "th"; } return day + ' ' + month + ' ' + intToStr(y + 1900); } uint32 AvalancheEngine::getTimeInSeconds() { TimeDate time; _system->getTimeAndDate(time); return time.tm_hour * 3600 + time.tm_min * 60 + time.tm_sec; } void AvalancheEngine::updateEvents() { Common::Event event; while (_eventMan->pollEvent(event)) { switch (event.type) { case Common::EVENT_LBUTTONDOWN: _holdLeftMouse = true; // Used in checkclick() and Menu::menu_link(). break; case Common::EVENT_LBUTTONUP: _holdLeftMouse = false; // Same as above. break; case Common::EVENT_KEYDOWN: if ((event.kbd.keycode == Common::KEYCODE_d) && (event.kbd.flags & Common::KBD_CTRL)) { // Attach to the debugger _console->attach(); _console->onFrame(); } else handleKeyDown(event); break; default: break; } } } bool AvalancheEngine::getEvent(Common::Event &event) { return _eventMan->pollEvent(event); } Common::Point AvalancheEngine::getMousePos() { return _eventMan->getMousePos(); } Common::Error AvalancheEngine::run() { Common::ErrorCode err = initialize(); if (err != Common::kNoError) return err; do { runAvalot(); #if 0 switch (_storage._operation) { case kRunShootemup: run("seu.avx", kJsb, kBflight, kNormal); break; case kRunDosshell: dosShell(); break; case kRunGhostroom: run("g-room.avx", kJsb, kNoBflight, kNormal); break; case kRunGolden: run("golden.avx", kJsb, kBflight, kMusical); break; } #endif } while (!_letMeOut && !shouldQuit()); return Common::kNoError; } #if 0 void AvalancheEngine::run(Common::String what, bool withJsb, bool withBflight, Elm how) { // Probably there'll be no need of this function, as all *.AVX-es will become classes. warning("STUB: run(%s)", what.c_str()); } Common::String AvalancheEngine::elmToStr(Elm how) { switch (how) { case kNormal: case kMusical: return Common::String("jsb"); case kRegi: return Common::String("REGI"); case kElmpoyten: return Common::String("ELMPOYTEN"); // Useless, but silent a warning default: return Common::String(""); } } // Same as keypressed1(). void AvalancheEngine::flushBuffer() { warning("STUB: flushBuffer()"); } void AvalancheEngine::dosShell() { warning("STUB: dosShell()"); } // Needed in dos_shell(). TODO: Remove later. Common::String AvalancheEngine::commandCom() { warning("STUB: commandCom()"); return ("STUB: commandCom()"); } // Needed for run_avalot()'s errors. TODO: Remove later. void AvalancheEngine::explain(byte error) { warning("STUB: explain()"); } // Needed later. void AvalancheEngine::quit() { cursorOn(); } #endif } // End of namespace Avalanche