diff options
Diffstat (limited to 'engines/dm/dm.cpp')
-rw-r--r-- | engines/dm/dm.cpp | 1038 |
1 files changed, 1038 insertions, 0 deletions
diff --git a/engines/dm/dm.cpp b/engines/dm/dm.cpp new file mode 100644 index 0000000000..25d741f376 --- /dev/null +++ b/engines/dm/dm.cpp @@ -0,0 +1,1038 @@ +/* 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. +* +*/ + +/* +* Based on the Reverse Engineering work of Christophe Fontanel, +* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/) +*/ + +#include "advancedDetector.h" + +#include "common/config-manager.h" +#include "common/scummsys.h" +#include "common/system.h" + +#include "common/debug.h" +#include "common/debug-channels.h" +#include "common/error.h" + +#include "common/file.h" +#include "common/events.h" +#include "common/array.h" +#include "common/algorithm.h" +#include "common/translation.h" + +#include "engines/util.h" +#include "engines/engine.h" + +#include "graphics/cursorman.h" +#include "graphics/palette.h" +#include "graphics/surface.h" + +#include "gui/saveload.h" + +#include "dm/dm.h" +#include "dm/gfx.h" +#include "dm/dungeonman.h" +#include "dm/eventman.h" +#include "dm/menus.h" +#include "dm/champion.h" +#include "dm/loadsave.h" +#include "dm/objectman.h" +#include "dm/inventory.h" +#include "dm/text.h" +#include "dm/movesens.h" +#include "dm/group.h" +#include "dm/timeline.h" +#include "dm/projexpl.h" +#include "dm/dialog.h" +#include "dm/sounds.h" + +namespace DM { + +bool DMEngine::isDemo() const { + return (bool)(_gameVersion->_desc.flags & ADGF_DEMO); +} + +Direction DMEngine::turnDirRight(int16 dir) { + Direction result = (Direction)((dir + 1) & 3); + return result; +} + +Direction DMEngine::returnOppositeDir(int16 dir) { + Direction result = (Direction)((dir + 2) & 3); + return result; +} + +Direction DMEngine::turnDirLeft(int16 dir) { + Direction result = (Direction)((dir + 3) & 3); + return result; +} + +bool DMEngine::isOrientedWestEast(int16 dir) { + return dir & 1; +} + +uint16 DMEngine::normalizeModulo4(int16 dir) { + return dir & 3; +} + +int32 DMEngine::filterTime(int32 mapTime) { + return mapTime & 0x00FFFFFF; +} + +int32 DMEngine::setMapAndTime(uint32 map, uint32 time) { + return (time | (map << 24)); +} + +uint16 DMEngine::getMap(int32 mapTime) { + return ((uint16)(mapTime >> 24)); +} + +int32 DMEngine::setMap(int32 mapTime, uint32 map) { + return ((mapTime & 0x00FFFFFF) | (map << 24)); +} + +Thing DMEngine::thingWithNewCell(Thing thing, int16 cell) { + return Thing(((thing.toUint16()) & 0x3FFF) | ((cell) << 14)); +} + +int16 DMEngine::getDistance(int16 mapx1, int16 mapy1, int16 mapx2, int16 mapy2) { + return ABS(mapx1 - mapx2) + ABS(mapy1 - mapy2); +} + +DMEngine::DMEngine(OSystem *syst, const DMADGameDescription *desc) : Engine(syst), _console(nullptr), _gameVersion(desc) { + // register random source + _rnd = new Common::RandomSource("dm"); + + _dungeonMan = nullptr; + _displayMan = nullptr; + _eventMan = nullptr; + _menuMan = nullptr; + _championMan = nullptr; + _objectMan = nullptr; + _inventoryMan = nullptr; + _textMan = nullptr; + _moveSens = nullptr; + _groupMan = nullptr; + _timeline = nullptr; + _projexpl = nullptr; + _displayMan = nullptr; + _sound = nullptr; + + _engineShouldQuit = false; + _dungeonId = 0; + + _gameMode = kDMModeLoadSavedGame; + _restartGameRequest = false; + _stopWaitingForPlayerInput = true; + _gameTimeTicking = false; + _restartGameAllowed = false; + _pressingEye = false; + _stopPressingEye = false; + _pressingMouth = false; + _stopPressingMouth = false; + _highlightBoxInversionRequested = false; + _projectileDisableMovementTicks = 0; + _lastProjectileDisabledMovementDirection = 0; + _gameWon = false; + _newPartyMapIndex = kDMMapIndexNone; + _setMousePointerToObjectInMainLoop = false; + _disabledMovementTicks = 0; + _gameTime = 0; + _stringBuildBuffer[0] = '\0'; + _waitForInputMaxVerticalBlankCount = 0; + _savedScreenForOpenEntranceDoors = nullptr; + for (uint16 i = 0; i < 10; ++i) + _entranceDoorAnimSteps[i] = nullptr; + _interfaceCredits = nullptr; + debug("DMEngine::DMEngine"); + + _saveThumbnail = nullptr; + _canLoadFromGMM = false; + _loadSaveSlotAtRuntime = -1; + _dialog = nullptr; +} + +DMEngine::~DMEngine() { + debug("DMEngine::~DMEngine"); + + // dispose of resources + delete _rnd; + delete _console; + delete _displayMan; + delete _dungeonMan; + delete _eventMan; + delete _menuMan; + delete _championMan; + delete _objectMan; + delete _inventoryMan; + delete _textMan; + delete _moveSens; + delete _groupMan; + delete _timeline; + delete _projexpl; + delete _dialog; + delete _sound; + + delete _saveThumbnail; + + delete[] _savedScreenForOpenEntranceDoors; + // clear debug channels + DebugMan.clearAllDebugChannels(); +} + +bool DMEngine::hasFeature(EngineFeature f) const { + return + (f == kSupportsSavingDuringRuntime) || + (f == kSupportsLoadingDuringRuntime); +} + +Common::Error DMEngine::loadGameState(int slot) { + if (loadgame(slot) != kDMLoadgameFailure) { + _displayMan->fillScreen(kDMColorBlack); + _displayMan->startEndFadeToPalette(_displayMan->_palDungeonView[0]); + _gameMode = kDMModeLoadSavedGame; + + startGame(); + _restartGameRequest = false; + _eventMan->hideMouse(); + _eventMan->discardAllInput(); + return Common::kNoError; + } + + return Common::kNoGameDataFoundError; +} + +bool DMEngine::canLoadGameStateCurrently() { + return _canLoadFromGMM; +} + +void DMEngine::delay(uint16 verticalBlank) { + for (uint16 i = 0; i < verticalBlank * 2; ++i) { + _eventMan->processInput(); + _displayMan->updateScreen(); + _system->delayMillis(10); // Google says most Amiga games had a refreshrate of 50 hz + } +} + +uint16 DMEngine::getScaledProduct(uint16 val, uint16 scale, uint16 vale2) { + return ((uint32)val * vale2) >> scale; +} + +void DMEngine::initializeGame() { + initMemoryManager(); + _displayMan->loadGraphics(); + _displayMan->initializeGraphicData(); + _displayMan->loadFloorSet(kDMFloorSetStone); + _displayMan->loadWallSet(kDMWallSetStone); + + _sound->loadSounds(); // @ F0506_AMIGA_AllocateData + + if (!ConfMan.hasKey("save_slot")) // skip drawing title if loading from launcher + drawTittle(); + + _textMan->initialize(); + _objectMan->loadObjectNames(); + _eventMan->initMouse(); + + int16 saveSlot = -1; + do { + // if loading from the launcher + if (ConfMan.hasKey("save_slot")) { + saveSlot = ConfMan.getInt("save_slot"); + } else { // else show the entrance + processEntrance(); + if (_engineShouldQuit) + return; + + if (_gameMode == kDMModeLoadSavedGame) { // if resume was clicked, bring up ScummVM load screen + GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false); + saveSlot = dialog->runModalWithCurrentTarget(); + delete dialog; + } + } + } while (loadgame(saveSlot) != kDMLoadgameSuccess); + + _displayMan->loadIntoBitmap(kDMGraphicIdxMenuSpellAreLines, _menuMan->_bitmapSpellAreaLines); // @ F0396_MENUS_LoadSpellAreaLinesBitmap + + // There was some memory wizardy for the Amiga platform, I skipped that part + _displayMan->allocateFlippedWallBitmaps(); + + startGame(); + if (_gameMode != kDMModeLoadSavedGame) + _moveSens->getMoveResult(Thing::_party, kDMMapXNotOnASquare, 0, _dungeonMan->_partyMapX, _dungeonMan->_partyMapY); + _eventMan->showMouse(); + _eventMan->discardAllInput(); +} + +void DMEngine::initMemoryManager() { + static uint16 palSwoosh[16] = {0x000, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0x000, 0xFFF, 0xAAA, 0xFFF, 0xAAA, 0x444, 0xFF0, 0xFF0}; // @ K0057_aui_Palette_Swoosh + + _displayMan->buildPaletteChangeCopperList(palSwoosh, palSwoosh); + for (uint16 i = 0; i < 16; ++i) { + _displayMan->_paletteTopAndBottomScreen[i] = _displayMan->_palDungeonView[0][i]; + _displayMan->_paletteMiddleScreen[i] = _displayMan->_palDungeonView[0][i]; + } +} + +void DMEngine::startGame() { + static Box boxScreenTop(0, 319, 0, 32); // @ G0061_s_Graphic562_Box_ScreenTop + static Box boxScreenRight(224, 319, 33, 169); // @ G0062_s_Graphic562_Box_ScreenRight + static Box boxScreenBottom(0, 319, 169, 199); // @ G0063_s_Graphic562_Box_ScreenBottom + + _pressingEye = false; + _stopPressingEye = false; + _pressingMouth = false; + _stopPressingMouth = false; + _highlightBoxInversionRequested = false; + _eventMan->_highlightBoxEnabled = false; + _championMan->_partyIsSleeping = false; + _championMan->_actingChampionOrdinal = indexToOrdinal(kDMChampionNone); + _menuMan->_actionAreaContainsIcons = true; + _eventMan->_useChampionIconOrdinalAsMousePointerBitmap = indexToOrdinal(kDMChampionNone); + + _eventMan->_primaryMouseInput = _eventMan->_primaryMouseInputInterface; + _eventMan->_secondaryMouseInput = _eventMan->_secondaryMouseInputMovement; + _eventMan->_primaryKeyboardInput = _eventMan->_primaryKeyboardInputInterface; + _eventMan->_secondaryKeyboardInput = _eventMan->_secondaryKeyboardInputMovement; + + processNewPartyMap(_dungeonMan->_partyMapIndex); + + if (_gameMode == kDMModeLoadSavedGame) { + _displayMan->startEndFadeToPalette(_displayMan->_paletteTopAndBottomScreen); + _displayMan->_useByteBoxCoordinates = false; + delay(1); + _displayMan->fillScreenBox(boxScreenTop, kDMColorBlack); + _displayMan->fillScreenBox(boxScreenRight, kDMColorBlack); + _displayMan->fillScreenBox(boxScreenBottom, kDMColorBlack); + } else { + _displayMan->_useByteBoxCoordinates = false; + _displayMan->fillScreenBox(boxScreenTop, kDMColorBlack); + _displayMan->fillScreenBox(boxScreenRight, kDMColorBlack); + _displayMan->fillScreenBox(boxScreenBottom, kDMColorBlack); + } + + _displayMan->buildPaletteChangeCopperList(_displayMan->_palDungeonView[0], _displayMan->_paletteTopAndBottomScreen); + _menuMan->drawMovementArrows(); + _championMan->resetDataToStartGame(); + _gameTimeTicking = true; +} + +void DMEngine::processNewPartyMap(uint16 mapIndex) { + _groupMan->removeAllActiveGroups(); + _dungeonMan->setCurrentMapAndPartyMap(mapIndex); + _displayMan->loadCurrentMapGraphics(); + _groupMan->addAllActiveGroups(); + _inventoryMan->setDungeonViewPalette(); +} + +Common::Error DMEngine::run() { + initConstants(); + + // scummvm/engine specific + initGraphics(320, 200, false); + _console = new Console(this); + _displayMan = new DisplayMan(this); + _dungeonMan = new DungeonMan(this); + _eventMan = new EventManager(this); + _menuMan = new MenuMan(this); + _championMan = new ChampionMan(this); + _objectMan = new ObjectMan(this); + _inventoryMan = new InventoryMan(this); + _textMan = new TextMan(this); + _moveSens = new MovesensMan(this); + _groupMan = new GroupMan(this); + _timeline = new Timeline(this); + _projexpl = new ProjExpl(this); + _dialog = new DialogMan(this); + _sound = SoundMan::getSoundMan(this, _gameVersion); + _displayMan->setUpScreens(320, 200); + + initializeGame(); + while (true) { + gameloop(); + + if (_engineShouldQuit) + return Common::kNoError; + + if (_loadSaveSlotAtRuntime == -1) + endGame(_championMan->_partyDead); + else { + loadGameState(_loadSaveSlotAtRuntime); + _menuMan->drawEnabledMenus(); + _displayMan->updateScreen(); + _loadSaveSlotAtRuntime = -1; + } + } + + return Common::kNoError; +} + +void DMEngine::gameloop() { + _canLoadFromGMM = true; + _waitForInputMaxVerticalBlankCount = 15; + while (true) { + if (_engineShouldQuit) { + _canLoadFromGMM = false; + return; + } + + // DEBUG CODE + for (int16 i = 0; i < _championMan->_partyChampionCount; ++i) { + Champion &champ = _championMan->_champions[i]; + if (_console->_debugGodmodeHP) + champ._currHealth = champ._maxHealth; + if (_console->_debugGodmodeMana) + champ._currMana = champ._maxMana; + if (_console->_debugGodmodeStamina) + champ._currStamina = champ._maxStamina; + } + + for (;;) { + + + if (_newPartyMapIndex != kDMMapIndexNone) { + processNewPartyMap(_newPartyMapIndex); + _moveSens->getMoveResult(Thing::_party, kDMMapXNotOnASquare, 0, _dungeonMan->_partyMapX, _dungeonMan->_partyMapY); + _newPartyMapIndex = kDMMapIndexNone; + _eventMan->discardAllInput(); + } + _timeline->processTimeline(); + + if (_newPartyMapIndex == kDMMapIndexNone) + break; + } + + if (!_inventoryMan->_inventoryChampionOrdinal && !_championMan->_partyIsSleeping) { + Box box(0, 223, 0, 135); + _displayMan->fillBoxBitmap(_displayMan->_bitmapViewport, box, kDMColorBlack, k112_byteWidthViewport, k136_heightViewport); // (possibly dummy code) + _displayMan->drawDungeon(_dungeonMan->_partyDir, _dungeonMan->_partyMapX, _dungeonMan->_partyMapY); + if (_setMousePointerToObjectInMainLoop) { + _setMousePointerToObjectInMainLoop = false; + _eventMan->showMouse(); + _eventMan->setPointerToObject(_objectMan->_objectIconForMousePointer); + _eventMan->hideMouse(); + + } + if (_eventMan->_refreshMousePointerInMainLoop) { + _eventMan->_refreshMousePointerInMainLoop = false; + _eventMan->_mousePointerBitmapUpdated = true; + _eventMan->showMouse(); + _eventMan->hideMouse(); + } + } + _eventMan->highlightBoxDisable(); + _sound->playPendingSound(); + _championMan->applyAndDrawPendingDamageAndWounds(); + if (_championMan->_partyDead) + break; + + _gameTime++; + + if (!(_gameTime & 511)) + _inventoryMan->decreaseTorchesLightPower(); + + if (_championMan->_party._freezeLifeTicks) + _championMan->_party._freezeLifeTicks -= 1; + + _menuMan->refreshActionAreaAndSetChampDirMaxDamageReceived(); + + if (!(_gameTime & (_championMan->_partyIsSleeping ? 15 : 63))) + _championMan->applyTimeEffects(); + + if (_disabledMovementTicks) + _disabledMovementTicks--; + + if (_projectileDisableMovementTicks) + _projectileDisableMovementTicks--; + + _textMan->clearExpiredRows(); + _stopWaitingForPlayerInput = false; + uint16 vblankCounter = 0; + do { + _eventMan->processInput(); + + if (_stopPressingEye) { + _pressingEye = false; + _stopPressingEye = false; + _inventoryMan->drawStopPressingEye(); + } else if (_stopPressingMouth) { + _pressingMouth = false; + _stopPressingMouth = false; + _inventoryMan->drawStopPressingMouth(); + } + + _eventMan->processCommandQueue(); + if (_engineShouldQuit || _loadSaveSlotAtRuntime != -1) { + _canLoadFromGMM = false; + return; + } + _displayMan->updateScreen(); + if (!_stopWaitingForPlayerInput) { + _eventMan->highlightBoxDisable(); + } + + if (++vblankCounter > _waitForInputMaxVerticalBlankCount) + _stopWaitingForPlayerInput = true; + else if (!_stopWaitingForPlayerInput) + _system->delayMillis(10); + + } while (!_stopWaitingForPlayerInput || !_gameTimeTicking); + } + _canLoadFromGMM = false; +} + +int16 DMEngine::ordinalToIndex(int16 val) { + return val - 1; +} + +int16 DMEngine::indexToOrdinal(int16 val) { + return val + 1; +} + +void DMEngine::processEntrance() { + _eventMan->_primaryMouseInput = _eventMan->_primaryMouseInputEntrance; + _eventMan->_secondaryMouseInput = nullptr; + _eventMan->_primaryKeyboardInput = nullptr; + _eventMan->_secondaryKeyboardInput = nullptr; + _entranceDoorAnimSteps[0] = new byte[128 * 161 * 12]; + for (uint16 idx = 1; idx < 8; idx++) + _entranceDoorAnimSteps[idx] = _entranceDoorAnimSteps[idx - 1] + 128 * 161; + + _entranceDoorAnimSteps[8] = _entranceDoorAnimSteps[7] + 128 * 161; + _entranceDoorAnimSteps[9] = _entranceDoorAnimSteps[8] + 128 * 161 * 2; + + _displayMan->loadIntoBitmap(kDMGraphicIdxEntranceRightDoor, _entranceDoorAnimSteps[4]); + _displayMan->loadIntoBitmap(kDMGraphicIdxEntranceLeftDoor, _entranceDoorAnimSteps[0]); + _interfaceCredits = _displayMan->getNativeBitmapOrGraphic(kDMGraphicIdxCredits); + _displayMan->_useByteBoxCoordinates = false; + Box displayBox(0, 100, 0, 160); + for (uint16 idx = 1; idx < 4; idx++) { + _displayMan->blitToBitmap(_entranceDoorAnimSteps[0], _entranceDoorAnimSteps[idx], displayBox, idx << 2, 0, k64_byteWidth, k64_byteWidth, kDMColorNoTransparency, 161, 161); + displayBox._rect.right -= 4; + } + displayBox._rect.right = 127; + for (uint16 idx = 5; idx < 8; idx++) { + displayBox._rect.left += 4; + _displayMan->blitToBitmap(_entranceDoorAnimSteps[4], _entranceDoorAnimSteps[idx], displayBox, 0, 0, k64_byteWidth, k64_byteWidth, kDMColorNoTransparency, 161, 161); + } + + do { + drawEntrance(); + _eventMan->showMouse(); + _eventMan->discardAllInput(); + _gameMode = kDMModeWaitingOnEntrance; + do { + _eventMan->processInput(); + if (_engineShouldQuit) + return; + _eventMan->processCommandQueue(); + _displayMan->updateScreen(); + } while (_gameMode == kDMModeWaitingOnEntrance); + } while (_gameMode == kDMModeEntranceDrawCredits); + + //Strangerke: CHECKME: Earlier versions were using G0566_puc_Graphic534_Sound01Switch + _sound->play(kDMSoundIndexSwitch, 112, 0x40, 0x40); + delay(20); + _eventMan->showMouse(); + if (_gameMode != kDMModeLoadSavedGame) + openEntranceDoors(); + + delete[] _entranceDoorAnimSteps[0]; + for (uint16 i = 0; i < 10; ++i) + _entranceDoorAnimSteps[i] = nullptr; +} + +void DMEngine::endGame(bool doNotDrawCreditsOnly) { + static Box boxEndgameRestartOuterEN(103, 217, 145, 159); + static Box boxEndgameRestartInnerEN(105, 215, 147, 157); + + static Box boxEndgameRestartOuterDE(82, 238, 145, 159); + static Box boxEndgameRestartInnerDE(84, 236, 147, 157); + + static Box boxEndgameRestartOuterFR(100, 220, 145, 159); + static Box boxEndgameRestartInnerFR(102, 218, 147, 157); + + Box restartOuterBox; + Box restartInnerBox; + + switch (getGameLanguage()) { // localized + default: + case Common::EN_ANY: + restartOuterBox = boxEndgameRestartOuterEN; + restartInnerBox = boxEndgameRestartInnerEN; + break; + case Common::DE_DEU: + restartOuterBox = boxEndgameRestartOuterDE; + restartInnerBox = boxEndgameRestartInnerDE; + break; + case Common::FR_FRA: + restartOuterBox = boxEndgameRestartOuterFR; + restartInnerBox = boxEndgameRestartInnerFR; + break; + } + + static Box theEndBox(120, 199, 95, 108); + static Box championMirrorBox(11, 74, 7, 49); + static Box championPortraitBox(27, 58, 13, 41); + + bool waitBeforeDrawingRestart = true; + + _eventMan->setMousePointerToNormal(k0_pointerArrow); + _eventMan->showMouse(); + _eventMan->_primaryMouseInput = nullptr; + _eventMan->_secondaryMouseInput = nullptr; + _eventMan->_primaryKeyboardInput = nullptr; + _eventMan->_secondaryKeyboardInput = nullptr; + if (doNotDrawCreditsOnly && !_gameWon) { + _sound->requestPlay(kDMSoundIndexScream, _dungeonMan->_partyMapX, _dungeonMan->_partyMapY, kDMSoundModePlayImmediately); + delay(240); + } + + if (_displayMan->_paletteSwitchingEnabled) { + uint16 oldPalTopAndBottomScreen[16]; + for (uint16 i = 0; i < 16; ++i) + oldPalTopAndBottomScreen[i] = _displayMan->_paletteTopAndBottomScreen[i]; + for (int i = 0; i <= 7; i++) { + delay(1); + for (int colIdx = 0; colIdx < 16; colIdx++) { + _displayMan->_paletteMiddleScreen[colIdx] = _displayMan->getDarkenedColor(_displayMan->_paletteMiddleScreen[colIdx]); + _displayMan->_paletteTopAndBottomScreen[colIdx] = _displayMan->getDarkenedColor(_displayMan->_paletteTopAndBottomScreen[colIdx]); + } + } + _displayMan->_paletteSwitchingEnabled = false; + delay(1); + for (uint16 i = 0; i < 16; ++i) + _displayMan->_paletteTopAndBottomScreen[i] = oldPalTopAndBottomScreen[i]; + } else + _displayMan->startEndFadeToPalette(_displayMan->_blankBuffer); + + uint16 darkBluePalette[16]; + if (doNotDrawCreditsOnly) { + if (_gameWon) { + // Strangerke: Related to portraits. Game data could be missing for earlier versions of the game. + _displayMan->fillScreen(kDMColorDarkestGray); + for (int16 championIndex = kDMChampionFirst; championIndex < _championMan->_partyChampionCount; championIndex++) { + int16 textPosY = championIndex * 48; + Champion *curChampion = &_championMan->_champions[championIndex]; + _displayMan->blitToScreen(_displayMan->getNativeBitmapOrGraphic(kDMGraphicIdxWallOrnChampMirror), &championMirrorBox, k32_byteWidth, kDMColorFlesh, 43); + _displayMan->blitToScreen(curChampion->_portrait, &championPortraitBox, k16_byteWidth, kDMColorDarkGary, 29); + _textMan->printEndGameString(87, textPosY += 14, kDMColorGold, curChampion->_name); + int textPosX = (6 * strlen(curChampion->_name)) + 87; + char championTitleFirstCharacter = curChampion->_title[0]; + if ((championTitleFirstCharacter != ',') && (championTitleFirstCharacter != ';') && (championTitleFirstCharacter != '-')) + textPosX += 6; + + _textMan->printEndGameString(textPosX, textPosY++, kDMColorGold, curChampion->_title); + for (int16 idx = kDMSkillFighter; idx <= kDMSkillWizard; idx++) { + uint16 skillLevel = MIN<uint16>(16, _championMan->getSkillLevel(championIndex, idx | (kDMIgnoreObjectModifiers | kDMIgnoreTemporaryExperience))); + if (skillLevel == 1) + continue; + + Common::String displStr = Common::String::format("%s %s", _inventoryMan->_skillLevelNames[skillLevel - 2], _championMan->_baseSkillName[idx]); + _textMan->printEndGameString(105, textPosY = textPosY + 8, kDMColorLightestGray, displStr.c_str()); + } + championMirrorBox._rect.top += 48; + championMirrorBox._rect.bottom += 48; + championPortraitBox._rect.top += 48; + championPortraitBox._rect.top += 48; + } + _displayMan->startEndFadeToPalette(_displayMan->_paletteTopAndBottomScreen); + _engineShouldQuit = true; + return; + } +T0444017: + _displayMan->fillScreen(kDMColorBlack); + _displayMan->blitToScreen(_displayMan->getNativeBitmapOrGraphic(kDMGraphicIdxTheEnd), &theEndBox, k40_byteWidth, kDMColorNoTransparency, 14); + for (uint16 i = 0; i < 16; ++i) + darkBluePalette[i] = D01_RGB_DARK_BLUE; + uint16 curPalette[16]; + for (uint16 i = 0; i < 15; ++i) + curPalette[i] = darkBluePalette[i]; + curPalette[15] = D09_RGB_WHITE; + _displayMan->startEndFadeToPalette(curPalette); + _displayMan->updateScreen(); + if (waitBeforeDrawingRestart) + delay(300); + + if (_restartGameAllowed) { + _displayMan->_useByteBoxCoordinates = false; + _displayMan->fillScreenBox(restartOuterBox, kDMColorDarkestGray); + _displayMan->fillScreenBox(restartInnerBox, kDMColorBlack); + + switch (getGameLanguage()) { // localized + default: + case Common::EN_ANY: + _textMan->printToLogicalScreen(110, 154, kDMColorCyan, kDMColorBlack, "RESTART THIS GAME"); + break; + case Common::DE_DEU: + _textMan->printToLogicalScreen(110, 154, kDMColorCyan, kDMColorBlack, "DIESES SPIEL NEU STARTEN"); + break; + case Common::FR_FRA: + _textMan->printToLogicalScreen(110, 154, kDMColorCyan, kDMColorBlack, "RECOMMENCER CE JEU"); + break; + } + + curPalette[1] = D03_RGB_PINK; + curPalette[4] = D09_RGB_WHITE; + _eventMan->_primaryMouseInput = _eventMan->_primaryMouseInputRestartGame; + _eventMan->discardAllInput(); + _eventMan->hideMouse(); + _displayMan->startEndFadeToPalette(curPalette); + for (int16 verticalBlankCount = 900; --verticalBlankCount && !_restartGameRequest; delay(1)) + _eventMan->processCommandQueue(); + + _eventMan->showMouse(); + if (_restartGameRequest) { + _displayMan->startEndFadeToPalette(darkBluePalette); + _displayMan->fillScreen(kDMColorBlack); + _displayMan->startEndFadeToPalette(_displayMan->_palDungeonView[0]); + _gameMode = kDMModeLoadSavedGame; + if (loadgame(1) != kDMLoadgameFailure) { + startGame(); + _restartGameRequest = false; + _eventMan->hideMouse(); + _eventMan->discardAllInput(); + return; + } + } + } + + _displayMan->startEndFadeToPalette(darkBluePalette); + } + Box box(0, 319, 0, 199); + _displayMan->blitToScreen(_displayMan->getNativeBitmapOrGraphic(kDMGraphicIdxCredits), &box, k160_byteWidthScreen, kDMColorNoTransparency, k200_heightScreen); + + _displayMan->startEndFadeToPalette(_displayMan->_palCredits); + _eventMan->waitForMouseOrKeyActivity(); + if (_engineShouldQuit) + return; + + if (_restartGameAllowed && doNotDrawCreditsOnly) { + waitBeforeDrawingRestart = false; + _displayMan->startEndFadeToPalette(darkBluePalette); + goto T0444017; + } + + _engineShouldQuit = true; + return; +} + + +void DMEngine::drawEntrance() { + static Box doorsUpperHalfBox = Box(0, 231, 0, 80); + static Box doorsLowerHalfBox = Box(0, 231, 81, 160); + static Box closedDoorLeftBox = Box(0, 104, 30, 190); + static Box closedDoorRightBox = Box(105, 231, 30, 190); + /* Atari ST: { 0x000, 0x333, 0x444, 0x420, 0x654, 0x210, 0x040, 0x050, 0x432, 0x700, 0x543, 0x321, 0x222, 0x555, 0x310, 0x777 }, RGB colors are different */ + static uint16 palEntrance[16] = {0x000, 0x666, 0x888, 0x840, 0xCA8, 0x0C0, 0x080, 0x0A0, 0x864, 0xF00, 0xA86, 0x642, 0x444, 0xAAA, 0x620, 0xFFF}; // @ G0020_aui_Graphic562_Palette_Entrance + + byte *microDungeonCurrentMapData[32]; + + _dungeonMan->_partyMapIndex = kDMMapIndexEntrance; + _displayMan->_drawFloorAndCeilingRequested = true; + _dungeonMan->_currMapWidth = 5; + _dungeonMan->_currMapHeight = 5; + _dungeonMan->_currMapData = microDungeonCurrentMapData; + + Map map; // uninitialized, won't be used + _dungeonMan->_currMap = ↦ + Square microDungeonSquares[25]; + for (uint16 i = 0; i < 25; ++i) + microDungeonSquares[i] = Square(kDMElementTypeWall, 0); + + for (int16 idx = 0; idx < 5; idx++) { + microDungeonCurrentMapData[idx] = (byte*)µDungeonSquares[idx * 5]; + microDungeonSquares[idx + 10] = Square(kDMElementTypeCorridor, 0); + } + microDungeonSquares[7] = Square(kDMElementTypeCorridor, 0); + _displayMan->startEndFadeToPalette(_displayMan->_blankBuffer); + + // note, a global variable is used here in the original + _displayMan->loadIntoBitmap(kDMGraphicIdxEntrance, _displayMan->_bitmapScreen); + _displayMan->drawDungeon(kDMDirSouth, 2, 0); + + if (!_savedScreenForOpenEntranceDoors) + _savedScreenForOpenEntranceDoors = new byte[k200_heightScreen * k160_byteWidthScreen * 2]; + memcpy(_savedScreenForOpenEntranceDoors, _displayMan->_bitmapScreen, 320 * 200); + + _displayMan->_useByteBoxCoordinates = false; + _displayMan->blitToBitmap(_displayMan->_bitmapScreen, _entranceDoorAnimSteps[8], doorsUpperHalfBox, 0, 30, k160_byteWidthScreen, k128_byteWidth, kDMColorNoTransparency, 200, 161); + _displayMan->_useByteBoxCoordinates = false; + _displayMan->blitToBitmap(_displayMan->_bitmapScreen, _entranceDoorAnimSteps[8], doorsLowerHalfBox, 0, 111, k160_byteWidthScreen, k128_byteWidth, kDMColorNoTransparency, 200, 161); + + _displayMan->blitToScreen(_entranceDoorAnimSteps[0], &closedDoorLeftBox, k64_byteWidth, kDMColorNoTransparency, 161); + _displayMan->blitToScreen(_entranceDoorAnimSteps[4], &closedDoorRightBox, k64_byteWidth, kDMColorNoTransparency, 161); + _displayMan->startEndFadeToPalette(palEntrance); +} + +void DMEngine::openEntranceDoors() { + Box rightDoorBox(109, 231, 30, 193); + byte *rightDoorBitmap = _displayMan->getNativeBitmapOrGraphic(kDMGraphicIdxEntranceRightDoor); + Box leftDoorBox(0, 100, 30, 193); + uint16 leftDoorBlitFrom = 0; + byte *leftDoorBitmap = _displayMan->getNativeBitmapOrGraphic(kDMGraphicIdxEntranceLeftDoor); + + Box screenBox(0, 319, 0, 199); + + for (uint16 animStep = 1; animStep < 32; ++animStep) { + if ((animStep % 3) == 1) { + // Strangerke: CHECKME: Earlier versions of the game were using G0565_puc_Graphic535_Sound02DoorRattle instead of k02_soundDOOR_RATTLE 2 + _sound->play(kDMSoundIndexDoorRattle, 145, 0x40, 0x40); + } + + _displayMan->blitToScreen(_savedScreenForOpenEntranceDoors, &screenBox, 160, kDMColorNoTransparency, 200); + _displayMan->blitToBitmap(leftDoorBitmap, _displayMan->_bitmapScreen, leftDoorBox, leftDoorBlitFrom, 0, 64, k160_byteWidthScreen, + kDMColorNoTransparency, 161, k200_heightScreen); + _displayMan->blitToBitmap(rightDoorBitmap, _displayMan->_bitmapScreen, rightDoorBox, 0, 0, 64, k160_byteWidthScreen, + kDMColorNoTransparency, 161, k200_heightScreen); + _eventMan->discardAllInput(); + _displayMan->updateScreen(); + + leftDoorBox._rect.right -= 4; + leftDoorBlitFrom += 4; + rightDoorBox._rect.left += 4; + + delay(3); + } + delete[] _savedScreenForOpenEntranceDoors; + _savedScreenForOpenEntranceDoors = nullptr; +} + +void DMEngine::drawTittle() { + static Box boxTitleStrikesBackDestination(0, 319, 118, 174); + static Box boxTitleStrikesBackSource(0, 319, 0, 56); + static Box boxTitlePresents(0, 319, 90, 105); + static Box boxTitleDungeonChaos(0, 319, 0, 79); + + _displayMan->_useByteBoxCoordinates = false; + + byte *allocatedMem = new byte[145600 * 2]; + byte *titleSteps = allocatedMem; + byte *bitmapTitle = titleSteps; + _displayMan->loadIntoBitmap(kDMGraphicIdxTitle, titleSteps); + + titleSteps += 320 * 200; + uint16 blitPalette[16]; + for (uint16 i = 0; i < 16; ++i) + blitPalette[i] = D01_RGB_DARK_BLUE; + + _displayMan->startEndFadeToPalette(blitPalette); + _displayMan->fillScreen(kDMColorBlack); + // uncomment this to draw 'Presents' + //_displayMan->f132_blitToBitmap(L1384_puc_Bitmap_Title, _displayMan->_g348_bitmapScreen, G0005_s_Graphic562_Box_Title_Presents, 0, 137, k160_byteWidthScreen, k160_byteWidthScreen, kM1_ColorNoTransparency, k200_heightScreen, k200_heightScreen); + blitPalette[15] = D09_RGB_WHITE; + _displayMan->startEndFadeToPalette(blitPalette); + byte *masterStrikesBack = titleSteps; + _displayMan->blitToBitmap(bitmapTitle, masterStrikesBack, boxTitleStrikesBackSource, 0, 80, k160_byteWidthScreen, k160_byteWidthScreen, kDMColorNoTransparency, 200, 57); + titleSteps += 320 * 57; + byte *bitmapDungeonChaos = titleSteps; /* Unreferenced on Atari ST */ + _displayMan->blitToBitmap(bitmapTitle, bitmapDungeonChaos, boxTitleDungeonChaos, 0, 0, k160_byteWidthScreen, k160_byteWidthScreen, kDMColorNoTransparency, 200, 80); + titleSteps += 320 * 80; + bitmapTitle = bitmapDungeonChaos; + uint16 destinationHeight = 12; + int16 destinationPixelWidth = 48; + byte *shrinkedTitle[20]; /* Only the first 18 entries are actually used */ + int16 blitCoordinates[20][5]; /* Only the first 18 entries are actually used */ + for (int16 i = 0; i < 18; i++) { + shrinkedTitle[i] = titleSteps; + _displayMan->blitToBitmapShrinkWithPalChange(bitmapTitle, titleSteps, 320, 80, destinationPixelWidth, destinationHeight, _displayMan->_palChangesNoChanges); + blitCoordinates[i][0] = (320 - destinationPixelWidth) / 2; + blitCoordinates[i][1] = blitCoordinates[i][0] + destinationPixelWidth - 1; + blitCoordinates[i][2] = (160 - destinationHeight) / 2; + blitCoordinates[i][3] = blitCoordinates[i][2] + destinationHeight - 1; + titleSteps += (blitCoordinates[i][4] = ((destinationPixelWidth + 15) / 16) * 8) * destinationHeight * 2; + destinationHeight += 4; + destinationPixelWidth += 16; + } + blitPalette[15] = D01_RGB_DARK_BLUE; + _displayMan->startEndFadeToPalette(blitPalette); + _displayMan->fillScreen(kDMColorBlack); + blitPalette[3] = D05_RGB_DARK_GOLD; + blitPalette[4] = D02_RGB_LIGHT_BROWN; + blitPalette[5] = D06_RGB_GOLD; + blitPalette[6] = D04_RGB_LIGHTER_BROWN; + blitPalette[8] = D08_RGB_YELLOW; + blitPalette[15] = D07_RGB_RED; + blitPalette[10] = D01_RGB_DARK_BLUE; + blitPalette[12] = D01_RGB_DARK_BLUE; + _displayMan->startEndFadeToPalette(blitPalette); + delay(1); + for (int16 i = 0; i < 18; i++) { + delay(2); + Box box(blitCoordinates[i][0], blitCoordinates[i][1], blitCoordinates[i][2], blitCoordinates[i][3]); + _displayMan->blitToBitmap(shrinkedTitle[i], _displayMan->_bitmapScreen, box, 0, 0, blitCoordinates[i][4], k160_byteWidthScreen, kDMColorNoTransparency, blitCoordinates[i][3] - blitCoordinates[i][2] + 1, k200_heightScreen); + } + delay(25); + _displayMan->blitToBitmap(masterStrikesBack, _displayMan->_bitmapScreen, boxTitleStrikesBackDestination, 0, 0, k160_byteWidthScreen, k160_byteWidthScreen, kDMColorBlack, 57, k200_heightScreen); + blitPalette[10] = D00_RGB_BLACK; + blitPalette[12] = D07_RGB_RED; + _displayMan->startEndFadeToPalette(blitPalette); + delete[] allocatedMem; + delay(75); +} + +void DMEngine::entranceDrawCredits() { + _eventMan->showMouse(); + _displayMan->startEndFadeToPalette(_displayMan->_blankBuffer); + _displayMan->loadIntoBitmap(kDMGraphicIdxCredits, _displayMan->_bitmapScreen); + _displayMan->startEndFadeToPalette(_displayMan->_palCredits); + delay(50); + _eventMan->waitForMouseOrKeyActivity(); + _gameMode = kDMModeEntranceDrawCredits; +} + +void DMEngine::fuseSequence() { + _gameWon = true; + if (_inventoryMan->_inventoryChampionOrdinal) + _inventoryMan->toggleInventory(kDMChampionCloseInventory); + + _eventMan->highlightBoxDisable(); + _championMan->_party._magicalLightAmount = 200; + _inventoryMan->setDungeonViewPalette(); + _championMan->_party._fireShieldDefense = _championMan->_party._spellShieldDefense = _championMan->_party._shieldDefense = 100; + _timeline->refreshAllChampionStatusBoxes(); + fuseSequenceUpdate(); + int16 lordChaosMapX = _dungeonMan->_partyMapX; + int16 lordChaosMapY = _dungeonMan->_partyMapY; + lordChaosMapX += _dirIntoStepCountEast[_dungeonMan->_partyDir], lordChaosMapY += _dirIntoStepCountNorth[_dungeonMan->_partyDir]; + Thing lordChaosThing = _groupMan->groupGetThing(lordChaosMapX, lordChaosMapY); + Group *lordGroup = (Group*)_dungeonMan->getThingData(lordChaosThing); + lordGroup->_health[0] = 10000; + _dungeonMan->setGroupCells(lordGroup, kDMCreatureTypeSingleCenteredCreature, _dungeonMan->_partyMapIndex); + _dungeonMan->setGroupDirections(lordGroup, returnOppositeDir(_dungeonMan->_partyDir), _dungeonMan->_partyMapIndex); + + bool removeFluxcagesFromLordChaosSquare = true; + int16 fluxCageMapX = _dungeonMan->_partyMapX; + int16 fluxcageMapY = _dungeonMan->_partyMapY; + + for (;;) { + Thing curThing = _dungeonMan->getSquareFirstObject(fluxCageMapX, fluxcageMapY); + while (curThing != Thing::_endOfList) { + if (curThing.getType() == kDMThingTypeExplosion) { + Explosion *curExplosion = (Explosion*)_dungeonMan->getThingData(curThing); + if (curExplosion->getType() == kDMExplosionTypeFluxcage) { + _dungeonMan->unlinkThingFromList(curThing, Thing(0), fluxCageMapX, fluxcageMapY); + curExplosion->setNextThing(Thing::_none); + continue; + } + } + curThing = _dungeonMan->getNextThing(curThing); + } + if (removeFluxcagesFromLordChaosSquare) { + removeFluxcagesFromLordChaosSquare = false; + fluxCageMapX = lordChaosMapX; + fluxcageMapY = lordChaosMapY; + } else + break; + } + fuseSequenceUpdate(); + for (int16 attackId = 55; attackId <= 255; attackId += 40) { + _projexpl->createExplosion(Thing::_explFireBall, attackId, lordChaosMapX, lordChaosMapY, kDMCreatureTypeSingleCenteredCreature); + fuseSequenceUpdate(); + } + _sound->requestPlay(kDMSoundIndexBuzz, lordChaosMapX, lordChaosMapY, kDMSoundModePlayIfPrioritized); + lordGroup->_type = kDMCreatureTypeLordOrder; + fuseSequenceUpdate(); + for (int16 attackId = 55; attackId <= 255; attackId += 40) { + _projexpl->createExplosion(Thing::_explHarmNonMaterial, attackId, lordChaosMapX, lordChaosMapY, kDMCreatureTypeSingleCenteredCreature); + fuseSequenceUpdate(); + } + for (int16 cycleCount = 3; cycleCount > 0; cycleCount--) { + for (int16 switchCount = 4; switchCount > 0; switchCount--) { + _sound->requestPlay(kDMSoundIndexBuzz, lordChaosMapX, lordChaosMapY, kDMSoundModePlayIfPrioritized); + lordGroup->_type = (switchCount & 0x0001) ? kDMCreatureTypeLordOrder : kDMCreatureTypeLordChaos; + for (int16 fuseSequenceUpdateCount = cycleCount - 1; fuseSequenceUpdateCount >= 0; fuseSequenceUpdateCount--) + fuseSequenceUpdate(); + } + } + _projexpl->createExplosion(Thing::_explFireBall, 255, lordChaosMapX, lordChaosMapY, kDMCreatureTypeSingleCenteredCreature); + _projexpl->createExplosion(Thing::_explHarmNonMaterial, 255, lordChaosMapX, lordChaosMapY, kDMCreatureTypeSingleCenteredCreature); + fuseSequenceUpdate(); + lordGroup->_type = kDMCreatureTypeGreyLord; + fuseSequenceUpdate(); + _displayMan->_doNotDrawFluxcagesDuringEndgame = true; + fuseSequenceUpdate(); + for (int16 curMapX = 0; curMapX < _dungeonMan->_currMapWidth; curMapX++) { + for (int curMapY = 0; curMapY < _dungeonMan->_currMapHeight; curMapY++) { + Thing curThing = _groupMan->groupGetThing(curMapX, curMapY); + if ((curThing != Thing::_endOfList) && ((curMapX != lordChaosMapX) || (curMapY != lordChaosMapY))) { + _groupMan->groupDelete(curMapX, curMapY); + } + } + } + fuseSequenceUpdate(); + /* Count and get list of text things located at 0, 0 in the current map. Their text is then printed as messages in the order specified by their first letter (which is not printed) */ + Thing curThing = _dungeonMan->getSquareFirstThing(0, 0); + int16 textStringThingCount = 0; + Thing textStringThings[8]; + while (curThing != Thing::_endOfList) { + if (curThing.getType() == kDMstringTypeText) + textStringThings[textStringThingCount++] = curThing; + + curThing = _dungeonMan->getNextThing(curThing); + } + char textFirstChar = 'A'; + int16 maxCount = textStringThingCount; + while (textStringThingCount--) { + for (int16 idx = 0; idx < maxCount; idx++) { + char decodedString[200]; + _dungeonMan->decodeText(decodedString, textStringThings[idx], (TextType)(kDMTextTypeMessage | kDMMaskDecodeEvenIfInvisible)); + if (decodedString[1] == textFirstChar) { + _textMan->clearAllRows(); + decodedString[1] = '\n'; /* New line */ + _textMan->printMessage(kDMColorWhite, &decodedString[1]); + fuseSequenceUpdate(); + delay(780); + textFirstChar++; + break; + } + } + } + + for (int16 attackId = 55; attackId <= 255; attackId += 40) { + _projexpl->createExplosion(Thing::_explHarmNonMaterial, attackId, lordChaosMapX, lordChaosMapY, kDMCreatureTypeSingleCenteredCreature); + fuseSequenceUpdate(); + } + + delay(600); + _restartGameAllowed = false; + endGame(true); +} + +void DMEngine::fuseSequenceUpdate() { + _timeline->processTimeline(); + _displayMan->drawDungeon(_dungeonMan->_partyDir, _dungeonMan->_partyMapX, _dungeonMan->_partyMapY); + _sound->playPendingSound(); + _eventMan->discardAllInput(); + _displayMan->updateScreen(); + delay(2); + _gameTime++; /* BUG0_71 Some timings are too short on fast computers. + The ending animation when Lord Chaos is fused plays too quickly because the execution speed is not limited */ +} + +Common::Language DMEngine::getGameLanguage() { + return _gameVersion->_desc.language; +} + +} // End of namespace DM |