1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22 
23 /*
24 * Based on the Reverse Engineering work of Christophe Fontanel,
25 * maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
26 */
27 
28 #include "advancedDetector.h"
29 
30 #include "common/config-manager.h"
31 #include "common/scummsys.h"
32 #include "common/system.h"
33 
34 #include "common/debug.h"
35 #include "common/debug-channels.h"
36 #include "common/error.h"
37 
38 #include "common/file.h"
39 #include "common/events.h"
40 #include "common/array.h"
41 #include "common/algorithm.h"
42 #include "common/translation.h"
43 
44 #include "engines/util.h"
45 #include "engines/engine.h"
46 
47 #include "graphics/cursorman.h"
48 #include "graphics/palette.h"
49 #include "graphics/surface.h"
50 
51 #include "gui/saveload.h"
52 
53 #include "dm/dm.h"
54 #include "dm/gfx.h"
55 #include "dm/dungeonman.h"
56 #include "dm/eventman.h"
57 #include "dm/menus.h"
58 #include "dm/champion.h"
59 #include "dm/loadsave.h"
60 #include "dm/objectman.h"
61 #include "dm/inventory.h"
62 #include "dm/text.h"
63 #include "dm/movesens.h"
64 #include "dm/group.h"
65 #include "dm/timeline.h"
66 #include "dm/projexpl.h"
67 #include "dm/dialog.h"
68 #include "dm/sounds.h"
69 
70 namespace DM {
71 
isDemo() const72 bool DMEngine::isDemo() const {
73 	return (bool)(_gameVersion->_desc.flags & ADGF_DEMO);
74 }
75 
turnDirRight(int16 dir)76 Direction DMEngine::turnDirRight(int16 dir) {
77 	Direction result = (Direction)((dir + 1) & 3);
78 	return result;
79 }
80 
returnOppositeDir(int16 dir)81 Direction DMEngine::returnOppositeDir(int16 dir) {
82 	Direction result = (Direction)((dir + 2) & 3);
83 	return result;
84 }
85 
turnDirLeft(int16 dir)86 Direction DMEngine::turnDirLeft(int16 dir) {
87 	Direction result = (Direction)((dir + 3) & 3);
88 	return result;
89 }
90 
isOrientedWestEast(int16 dir)91 bool DMEngine::isOrientedWestEast(int16 dir) {
92 	return dir & 1;
93 }
94 
normalizeModulo4(int16 dir)95 uint16 DMEngine::normalizeModulo4(int16 dir) {
96 	return dir & 3;
97 }
98 
filterTime(int32 mapTime)99 int32 DMEngine::filterTime(int32 mapTime) {
100 	return mapTime & 0x00FFFFFF;
101 }
102 
setMapAndTime(uint32 map,uint32 time)103 int32 DMEngine::setMapAndTime(uint32 map, uint32 time) {
104 	return (time | (map << 24));
105 }
106 
getMap(int32 mapTime)107 uint16 DMEngine::getMap(int32 mapTime) {
108 	return ((uint16)(mapTime >> 24));
109 }
110 
setMap(int32 mapTime,uint32 map)111 int32 DMEngine::setMap(int32 mapTime, uint32 map) {
112 	return ((mapTime & 0x00FFFFFF) | (map << 24));
113 }
114 
thingWithNewCell(Thing thing,int16 cell)115 Thing DMEngine::thingWithNewCell(Thing thing, int16 cell) {
116 	return Thing(((thing.toUint16()) & 0x3FFF) | ((cell) << 14));
117 }
118 
getDistance(int16 mapx1,int16 mapy1,int16 mapx2,int16 mapy2)119 int16 DMEngine::getDistance(int16 mapx1, int16 mapy1, int16 mapx2, int16 mapy2) {
120 	return ABS(mapx1 - mapx2) + ABS(mapy1 - mapy2);
121 }
122 
DMEngine(OSystem * syst,const DMADGameDescription * desc)123 DMEngine::DMEngine(OSystem *syst, const DMADGameDescription *desc) :
124 			Engine(syst), _console(nullptr), _gameVersion(desc),
125 			_thingNone(0), _thingEndOfList(0xFFFE), _thingFirstExplosion(0xFF80),
126 			_thingExplFireBall(0xFF80), _thingExplSlime(0xFF81), _thingExplLightningBolt(0xFF82),
127 			_thingExplHarmNonMaterial(0xFF83), _thingExplOpenDoor(0xFF84), _thingExplPoisonBolt(0xFF86),
128 			_thingExplPoisonCloud(0xFF87), _thingExplSmoke(0xFFA8), _thingExplFluxcage(0xFFB2),
129 			_thingExplRebirthStep1(0xFFE4), _thingExplRebirthStep2(0xFFE5), _thingParty(0xFFFF)
130 	{
131 	// register random source
132 	_rnd = new Common::RandomSource("dm");
133 
134 	_dungeonMan = nullptr;
135 	_displayMan = nullptr;
136 	_eventMan = nullptr;
137 	_menuMan = nullptr;
138 	_championMan = nullptr;
139 	_objectMan = nullptr;
140 	_inventoryMan = nullptr;
141 	_textMan = nullptr;
142 	_moveSens = nullptr;
143 	_groupMan = nullptr;
144 	_timeline = nullptr;
145 	_projexpl = nullptr;
146 	_displayMan = nullptr;
147 	_sound = nullptr;
148 
149 	_engineShouldQuit = false;
150 	_dungeonId = 0;
151 
152 	_gameMode = kDMModeLoadSavedGame;
153 	_restartGameRequest = false;
154 	_stopWaitingForPlayerInput = true;
155 	_gameTimeTicking = false;
156 	_restartGameAllowed = false;
157 	_pressingEye = false;
158 	_stopPressingEye = false;
159 	_pressingMouth = false;
160 	_stopPressingMouth = false;
161 	_highlightBoxInversionRequested = false;
162 	_projectileDisableMovementTicks = 0;
163 	_lastProjectileDisabledMovementDirection = 0;
164 	_gameWon = false;
165 	_newPartyMapIndex = kDMMapIndexNone;
166 	_setMousePointerToObjectInMainLoop = false;
167 	_disabledMovementTicks = 0;
168 	_gameTime = 0;
169 	_stringBuildBuffer[0] = '\0';
170 	_waitForInputMaxVerticalBlankCount = 0;
171 	_savedScreenForOpenEntranceDoors = nullptr;
172 	for (uint16 i = 0; i < 10; ++i)
173 		_entranceDoorAnimSteps[i] = nullptr;
174 	_interfaceCredits = nullptr;
175 	debug("DMEngine::DMEngine");
176 
177 	_saveThumbnail = nullptr;
178 	_canLoadFromGMM = false;
179 	_loadSaveSlotAtRuntime = -1;
180 	_dialog = nullptr;
181 }
182 
~DMEngine()183 DMEngine::~DMEngine() {
184 	debug("DMEngine::~DMEngine");
185 
186 	// dispose of resources
187 	delete _rnd;
188 	//delete _console; Debugger is deleted by Engine
189 	delete _displayMan;
190 	delete _dungeonMan;
191 	delete _eventMan;
192 	delete _menuMan;
193 	delete _championMan;
194 	delete _objectMan;
195 	delete _inventoryMan;
196 	delete _textMan;
197 	delete _moveSens;
198 	delete _groupMan;
199 	delete _timeline;
200 	delete _projexpl;
201 	delete _dialog;
202 	delete _sound;
203 
204 	delete _saveThumbnail;
205 
206 	delete[] _savedScreenForOpenEntranceDoors;
207 }
208 
hasFeature(EngineFeature f) const209 bool DMEngine::hasFeature(EngineFeature f) const {
210 	return
211 		(f == kSupportsSavingDuringRuntime) ||
212 		(f == kSupportsLoadingDuringRuntime);
213 }
214 
loadGameState(int slot)215 Common::Error DMEngine::loadGameState(int slot) {
216 	if (loadgame(slot) != kDMLoadgameFailure) {
217 		_displayMan->fillScreen(kDMColorBlack);
218 		_displayMan->startEndFadeToPalette(_displayMan->_palDungeonView[0]);
219 		_gameMode = kDMModeLoadSavedGame;
220 
221 		startGame();
222 		_restartGameRequest = false;
223 		_eventMan->hideMouse();
224 		_eventMan->discardAllInput();
225 		return Common::kNoError;
226 	}
227 
228 	return Common::kNoGameDataFoundError;
229 }
230 
canLoadGameStateCurrently()231 bool DMEngine::canLoadGameStateCurrently() {
232 	return _canLoadFromGMM;
233 }
234 
delay(uint16 verticalBlank)235 void DMEngine::delay(uint16 verticalBlank) {
236 	for (uint16 i = 0; i < verticalBlank * 2; ++i) {
237 		_eventMan->processInput();
238 		_displayMan->updateScreen();
239 		_system->delayMillis(10); // Google says most Amiga games had a refreshrate of 50 hz
240 	}
241 }
242 
getScaledProduct(uint16 val,uint16 scale,uint16 vale2)243 uint16 DMEngine::getScaledProduct(uint16 val, uint16 scale, uint16 vale2) {
244 	return ((uint32)val * vale2) >> scale;
245 }
246 
initializeGame()247 void DMEngine::initializeGame() {
248 	initMemoryManager();
249 	_displayMan->loadGraphics();
250 	_displayMan->initializeGraphicData();
251 	_displayMan->loadFloorSet(kDMFloorSetStone);
252 	_displayMan->loadWallSet(kDMWallSetStone);
253 
254 	_sound->loadSounds(); // @ F0506_AMIGA_AllocateData
255 
256 	if (!ConfMan.hasKey("save_slot")) // skip drawing title if loading from launcher
257 		drawTittle();
258 
259 	_textMan->initialize();
260 	_objectMan->loadObjectNames();
261 	_eventMan->initMouse();
262 
263 	int16 saveSlot = -1;
264 	do {
265 		// if loading from the launcher
266 		if (ConfMan.hasKey("save_slot")) {
267 			saveSlot = ConfMan.getInt("save_slot");
268 		} else { // else show the entrance
269 			processEntrance();
270 			if (_engineShouldQuit)
271 				return;
272 
273 			if (_gameMode == kDMModeLoadSavedGame) { // if resume was clicked, bring up ScummVM load screen
274 				GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false);
275 				saveSlot = dialog->runModalWithCurrentTarget();
276 				delete dialog;
277 			}
278 		}
279 	} while (loadgame(saveSlot) != kDMLoadgameSuccess);
280 
281 	_displayMan->loadIntoBitmap(kDMGraphicIdxMenuSpellAreLines, _menuMan->_bitmapSpellAreaLines); // @ F0396_MENUS_LoadSpellAreaLinesBitmap
282 
283 	// There was some memory wizardy for the Amiga platform, I skipped that part
284 	_displayMan->allocateFlippedWallBitmaps();
285 
286 	startGame();
287 	if (_gameMode != kDMModeLoadSavedGame)
288 		_moveSens->getMoveResult(_thingParty, kDMMapXNotOnASquare, 0, _dungeonMan->_partyMapX, _dungeonMan->_partyMapY);
289 	_eventMan->showMouse();
290 	_eventMan->discardAllInput();
291 }
292 
initMemoryManager()293 void DMEngine::initMemoryManager() {
294 	static uint16 palSwoosh[16] = {0x000, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0x000, 0xFFF, 0xAAA, 0xFFF, 0xAAA, 0x444, 0xFF0, 0xFF0}; // @ K0057_aui_Palette_Swoosh
295 
296 	_displayMan->buildPaletteChangeCopperList(palSwoosh, palSwoosh);
297 	for (uint16 i = 0; i < 16; ++i) {
298 		_displayMan->_paletteTopAndBottomScreen[i] = _displayMan->_palDungeonView[0][i];
299 		_displayMan->_paletteMiddleScreen[i] = _displayMan->_palDungeonView[0][i];
300 	}
301 }
302 
startGame()303 void DMEngine::startGame() {
304 	static Box boxScreenTop(0, 319, 0, 32); // @ G0061_s_Graphic562_Box_ScreenTop
305 	static Box boxScreenRight(224, 319, 33, 169); // @ G0062_s_Graphic562_Box_ScreenRight
306 	static Box boxScreenBottom(0, 319, 169, 199); // @ G0063_s_Graphic562_Box_ScreenBottom
307 
308 	_pressingEye = false;
309 	_stopPressingEye = false;
310 	_pressingMouth = false;
311 	_stopPressingMouth = false;
312 	_highlightBoxInversionRequested = false;
313 	_eventMan->_highlightBoxEnabled = false;
314 	_championMan->_partyIsSleeping = false;
315 	_championMan->_actingChampionOrdinal = indexToOrdinal(kDMChampionNone);
316 	_menuMan->_actionAreaContainsIcons = true;
317 	_eventMan->_useChampionIconOrdinalAsMousePointerBitmap = indexToOrdinal(kDMChampionNone);
318 
319 	_eventMan->_primaryMouseInput = _eventMan->_primaryMouseInputInterface;
320 	_eventMan->_secondaryMouseInput = _eventMan->_secondaryMouseInputMovement;
321 	_eventMan->_primaryKeyboardInput = _eventMan->_primaryKeyboardInputInterface;
322 	_eventMan->_secondaryKeyboardInput = _eventMan->_secondaryKeyboardInputMovement;
323 
324 	processNewPartyMap(_dungeonMan->_partyMapIndex);
325 
326 	if (_gameMode == kDMModeLoadSavedGame) {
327 		_displayMan->startEndFadeToPalette(_displayMan->_paletteTopAndBottomScreen);
328 		_displayMan->_useByteBoxCoordinates = false;
329 		delay(1);
330 		_displayMan->fillScreenBox(boxScreenTop, kDMColorBlack);
331 		_displayMan->fillScreenBox(boxScreenRight, kDMColorBlack);
332 		_displayMan->fillScreenBox(boxScreenBottom, kDMColorBlack);
333 	} else {
334 		_displayMan->_useByteBoxCoordinates = false;
335 		_displayMan->fillScreenBox(boxScreenTop, kDMColorBlack);
336 		_displayMan->fillScreenBox(boxScreenRight, kDMColorBlack);
337 		_displayMan->fillScreenBox(boxScreenBottom, kDMColorBlack);
338 	}
339 
340 	_displayMan->buildPaletteChangeCopperList(_displayMan->_palDungeonView[0], _displayMan->_paletteTopAndBottomScreen);
341 	_menuMan->drawMovementArrows();
342 	_championMan->resetDataToStartGame();
343 	_gameTimeTicking = true;
344 }
345 
processNewPartyMap(uint16 mapIndex)346 void DMEngine::processNewPartyMap(uint16 mapIndex) {
347 	_groupMan->removeAllActiveGroups();
348 	_dungeonMan->setCurrentMapAndPartyMap(mapIndex);
349 	_displayMan->loadCurrentMapGraphics();
350 	_groupMan->addAllActiveGroups();
351 	_inventoryMan->setDungeonViewPalette();
352 }
353 
run()354 Common::Error DMEngine::run() {
355 	initConstants();
356 
357 	// scummvm/engine specific
358 	initGraphics(320, 200);
359 	_console = new Console(this);
360 	setDebugger(_console);
361 	_displayMan = new DisplayMan(this);
362 	_dungeonMan = new DungeonMan(this);
363 	_eventMan = new EventManager(this);
364 	_menuMan = new MenuMan(this);
365 	_championMan = new ChampionMan(this);
366 	_objectMan = new ObjectMan(this);
367 	_inventoryMan = new InventoryMan(this);
368 	_textMan = new TextMan(this);
369 	_moveSens = new MovesensMan(this);
370 	_groupMan = new GroupMan(this);
371 	_timeline = new Timeline(this);
372 	_projexpl = new ProjExpl(this);
373 	_dialog = new DialogMan(this);
374 	_sound = SoundMan::getSoundMan(this, _gameVersion);
375 	_displayMan->setUpScreens(320, 200);
376 
377 	initializeGame();
378 	while (true) {
379 		gameloop();
380 
381 		if (_engineShouldQuit)
382 			return Common::kNoError;
383 
384 		if (_loadSaveSlotAtRuntime == -1)
385 			endGame(_championMan->_partyDead);
386 		else {
387 			loadGameState(_loadSaveSlotAtRuntime);
388 			_menuMan->drawEnabledMenus();
389 			_displayMan->updateScreen();
390 			_loadSaveSlotAtRuntime = -1;
391 		}
392 	}
393 
394 	return Common::kNoError;
395 }
396 
gameloop()397 void DMEngine::gameloop() {
398 	_canLoadFromGMM = true;
399 	_waitForInputMaxVerticalBlankCount = 15;
400 	while (true) {
401 		if (_engineShouldQuit) {
402 			_canLoadFromGMM = false;
403 			return;
404 		}
405 
406 		// DEBUG CODE
407 		for (int16 i = 0; i < _championMan->_partyChampionCount; ++i) {
408 			Champion &champ = _championMan->_champions[i];
409 			if (_console->_debugGodmodeHP)
410 				champ._currHealth = champ._maxHealth;
411 			if (_console->_debugGodmodeMana)
412 				champ._currMana = champ._maxMana;
413 			if (_console->_debugGodmodeStamina)
414 				champ._currStamina = champ._maxStamina;
415 		}
416 
417 		for (;;) {
418 
419 
420 			if (_newPartyMapIndex != kDMMapIndexNone) {
421 				processNewPartyMap(_newPartyMapIndex);
422 				_moveSens->getMoveResult(_thingParty, kDMMapXNotOnASquare, 0, _dungeonMan->_partyMapX, _dungeonMan->_partyMapY);
423 				_newPartyMapIndex = kDMMapIndexNone;
424 				_eventMan->discardAllInput();
425 			}
426 			_timeline->processTimeline();
427 
428 			if (_newPartyMapIndex == kDMMapIndexNone)
429 				break;
430 		}
431 
432 		if (!_inventoryMan->_inventoryChampionOrdinal && !_championMan->_partyIsSleeping) {
433 			Box box(0, 223, 0, 135);
434 			_displayMan->fillBoxBitmap(_displayMan->_bitmapViewport, box, kDMColorBlack, k112_byteWidthViewport, k136_heightViewport); // (possibly dummy code)
435 			_displayMan->drawDungeon(_dungeonMan->_partyDir, _dungeonMan->_partyMapX, _dungeonMan->_partyMapY);
436 			if (_setMousePointerToObjectInMainLoop) {
437 				_setMousePointerToObjectInMainLoop = false;
438 				_eventMan->showMouse();
439 				_eventMan->setPointerToObject(_objectMan->_objectIconForMousePointer);
440 				_eventMan->hideMouse();
441 
442 			}
443 			if (_eventMan->_refreshMousePointerInMainLoop) {
444 				_eventMan->_refreshMousePointerInMainLoop = false;
445 				_eventMan->_mousePointerBitmapUpdated = true;
446 				_eventMan->showMouse();
447 				_eventMan->hideMouse();
448 			}
449 		}
450 		_eventMan->highlightBoxDisable();
451 		_sound->playPendingSound();
452 		_championMan->applyAndDrawPendingDamageAndWounds();
453 		if (_championMan->_partyDead)
454 			break;
455 
456 		_gameTime++;
457 
458 		if (!(_gameTime & 511))
459 			_inventoryMan->decreaseTorchesLightPower();
460 
461 		if (_championMan->_party._freezeLifeTicks)
462 			_championMan->_party._freezeLifeTicks -= 1;
463 
464 		_menuMan->refreshActionAreaAndSetChampDirMaxDamageReceived();
465 
466 		if (!(_gameTime & (_championMan->_partyIsSleeping ? 15 : 63)))
467 			_championMan->applyTimeEffects();
468 
469 		if (_disabledMovementTicks)
470 			_disabledMovementTicks--;
471 
472 		if (_projectileDisableMovementTicks)
473 			_projectileDisableMovementTicks--;
474 
475 		_textMan->clearExpiredRows();
476 		_stopWaitingForPlayerInput = false;
477 		uint16 vblankCounter = 0;
478 		do {
479 			_eventMan->processInput();
480 
481 			if (_stopPressingEye) {
482 				_pressingEye = false;
483 				_stopPressingEye = false;
484 				_inventoryMan->drawStopPressingEye();
485 			} else if (_stopPressingMouth) {
486 				_pressingMouth = false;
487 				_stopPressingMouth = false;
488 				_inventoryMan->drawStopPressingMouth();
489 			}
490 
491 			_eventMan->processCommandQueue();
492 			if (_engineShouldQuit || _loadSaveSlotAtRuntime != -1) {
493 				_canLoadFromGMM = false;
494 				return;
495 			}
496 			_displayMan->updateScreen();
497 			if (!_stopWaitingForPlayerInput) {
498 				_eventMan->highlightBoxDisable();
499 			}
500 
501 			if (++vblankCounter > _waitForInputMaxVerticalBlankCount)
502 				_stopWaitingForPlayerInput = true;
503 			else if (!_stopWaitingForPlayerInput)
504 				_system->delayMillis(10);
505 
506 		} while (!_stopWaitingForPlayerInput || !_gameTimeTicking);
507 	}
508 	_canLoadFromGMM = false;
509 }
510 
ordinalToIndex(int16 val)511 int16 DMEngine::ordinalToIndex(int16 val) {
512 	return val - 1;
513 }
514 
indexToOrdinal(int16 val)515 int16 DMEngine::indexToOrdinal(int16 val) {
516 	return val + 1;
517 }
518 
processEntrance()519 void DMEngine::processEntrance() {
520 	_eventMan->_primaryMouseInput = _eventMan->_primaryMouseInputEntrance;
521 	_eventMan->_secondaryMouseInput = nullptr;
522 	_eventMan->_primaryKeyboardInput = nullptr;
523 	_eventMan->_secondaryKeyboardInput = nullptr;
524 	_entranceDoorAnimSteps[0] = new byte[128 * 161 * 12];
525 	for (uint16 idx = 1; idx < 8; idx++)
526 		_entranceDoorAnimSteps[idx] = _entranceDoorAnimSteps[idx - 1] + 128 * 161;
527 
528 	_entranceDoorAnimSteps[8] = _entranceDoorAnimSteps[7] + 128 * 161;
529 	_entranceDoorAnimSteps[9] = _entranceDoorAnimSteps[8] + 128 * 161 * 2;
530 
531 	_displayMan->loadIntoBitmap(kDMGraphicIdxEntranceRightDoor, _entranceDoorAnimSteps[4]);
532 	_displayMan->loadIntoBitmap(kDMGraphicIdxEntranceLeftDoor, _entranceDoorAnimSteps[0]);
533 	_interfaceCredits = _displayMan->getNativeBitmapOrGraphic(kDMGraphicIdxCredits);
534 	_displayMan->_useByteBoxCoordinates = false;
535 	Box displayBox(0, 100, 0, 160);
536 	for (uint16 idx = 1; idx < 4; idx++) {
537 		_displayMan->blitToBitmap(_entranceDoorAnimSteps[0], _entranceDoorAnimSteps[idx], displayBox, idx << 2, 0, k64_byteWidth, k64_byteWidth, kDMColorNoTransparency, 161, 161);
538 		displayBox._rect.right -= 4;
539 	}
540 	displayBox._rect.right = 127;
541 	for (uint16 idx = 5; idx < 8; idx++) {
542 		displayBox._rect.left += 4;
543 		_displayMan->blitToBitmap(_entranceDoorAnimSteps[4], _entranceDoorAnimSteps[idx], displayBox, 0, 0, k64_byteWidth, k64_byteWidth, kDMColorNoTransparency, 161, 161);
544 	}
545 
546 	do {
547 		drawEntrance();
548 		_eventMan->showMouse();
549 		_eventMan->discardAllInput();
550 		_gameMode = kDMModeWaitingOnEntrance;
551 		do {
552 			_eventMan->processInput();
553 			if (_engineShouldQuit)
554 				return;
555 			_eventMan->processCommandQueue();
556 			_displayMan->updateScreen();
557 		} while (_gameMode == kDMModeWaitingOnEntrance);
558 	} while (_gameMode == kDMModeEntranceDrawCredits);
559 
560 	//Strangerke: CHECKME: Earlier versions were using G0566_puc_Graphic534_Sound01Switch
561 	_sound->play(kDMSoundIndexSwitch, 112, 0x40, 0x40);
562 	delay(20);
563 	_eventMan->showMouse();
564 	if (_gameMode != kDMModeLoadSavedGame)
565 		openEntranceDoors();
566 
567 	delete[] _entranceDoorAnimSteps[0];
568 	for (uint16 i = 0; i < 10; ++i)
569 		_entranceDoorAnimSteps[i] = nullptr;
570 }
571 
endGame(bool doNotDrawCreditsOnly)572 void DMEngine::endGame(bool doNotDrawCreditsOnly) {
573 	static Box boxEndgameRestartOuterEN(103, 217, 145, 159);
574 	static Box boxEndgameRestartInnerEN(105, 215, 147, 157);
575 
576 	static Box boxEndgameRestartOuterDE(82, 238, 145, 159);
577 	static Box boxEndgameRestartInnerDE(84, 236, 147, 157);
578 
579 	static Box boxEndgameRestartOuterFR(100, 220, 145, 159);
580 	static Box boxEndgameRestartInnerFR(102, 218, 147, 157);
581 
582 	Box restartOuterBox;
583 	Box restartInnerBox;
584 
585 	switch (getGameLanguage()) { // localized
586 	default:
587 	case Common::EN_ANY:
588 		restartOuterBox = boxEndgameRestartOuterEN;
589 		restartInnerBox = boxEndgameRestartInnerEN;
590 		break;
591 	case Common::DE_DEU:
592 		restartOuterBox = boxEndgameRestartOuterDE;
593 		restartInnerBox = boxEndgameRestartInnerDE;
594 		break;
595 	case Common::FR_FRA:
596 		restartOuterBox = boxEndgameRestartOuterFR;
597 		restartInnerBox = boxEndgameRestartInnerFR;
598 		break;
599 	}
600 
601 	static Box theEndBox(120, 199, 95, 108);
602 	static Box championMirrorBox(11, 74, 7, 49);
603 	static Box championPortraitBox(27, 58, 13, 41);
604 
605 	bool waitBeforeDrawingRestart = true;
606 
607 	_eventMan->setMousePointerToNormal(k0_pointerArrow);
608 	_eventMan->showMouse();
609 	_eventMan->_primaryMouseInput = nullptr;
610 	_eventMan->_secondaryMouseInput = nullptr;
611 	_eventMan->_primaryKeyboardInput = nullptr;
612 	_eventMan->_secondaryKeyboardInput = nullptr;
613 	if (doNotDrawCreditsOnly && !_gameWon) {
614 		_sound->requestPlay(kDMSoundIndexScream, _dungeonMan->_partyMapX, _dungeonMan->_partyMapY, kDMSoundModePlayImmediately);
615 		delay(240);
616 	}
617 
618 	if (_displayMan->_paletteSwitchingEnabled) {
619 		uint16 oldPalTopAndBottomScreen[16];
620 		for (uint16 i = 0; i < 16; ++i)
621 			oldPalTopAndBottomScreen[i] = _displayMan->_paletteTopAndBottomScreen[i];
622 		for (int i = 0; i <= 7; i++) {
623 			delay(1);
624 			for (int colIdx = 0; colIdx < 16; colIdx++) {
625 				_displayMan->_paletteMiddleScreen[colIdx] = _displayMan->getDarkenedColor(_displayMan->_paletteMiddleScreen[colIdx]);
626 				_displayMan->_paletteTopAndBottomScreen[colIdx] = _displayMan->getDarkenedColor(_displayMan->_paletteTopAndBottomScreen[colIdx]);
627 			}
628 		}
629 		_displayMan->_paletteSwitchingEnabled = false;
630 		delay(1);
631 		for (uint16 i = 0; i < 16; ++i)
632 			_displayMan->_paletteTopAndBottomScreen[i] = oldPalTopAndBottomScreen[i];
633 	} else
634 		_displayMan->startEndFadeToPalette(_displayMan->_blankBuffer);
635 
636 	uint16 darkBluePalette[16];
637 	if (doNotDrawCreditsOnly) {
638 		if (_gameWon) {
639 			// Strangerke: Related to portraits. Game data could be missing for earlier versions of the game.
640 			_displayMan->fillScreen(kDMColorDarkestGray);
641 			for (int16 championIndex = kDMChampionFirst; championIndex < _championMan->_partyChampionCount; championIndex++) {
642 				int16 textPosY = championIndex * 48;
643 				Champion *curChampion = &_championMan->_champions[championIndex];
644 				_displayMan->blitToScreen(_displayMan->getNativeBitmapOrGraphic(kDMGraphicIdxWallOrnChampMirror), &championMirrorBox, k32_byteWidth, kDMColorFlesh, 43);
645 				_displayMan->blitToScreen(curChampion->_portrait, &championPortraitBox, k16_byteWidth, kDMColorDarkGary, 29);
646 				_textMan->printEndGameString(87, textPosY += 14, kDMColorGold, curChampion->_name);
647 				int textPosX = (6 * strlen(curChampion->_name)) + 87;
648 				char championTitleFirstCharacter = curChampion->_title[0];
649 				if ((championTitleFirstCharacter != ',') && (championTitleFirstCharacter != ';') && (championTitleFirstCharacter != '-'))
650 					textPosX += 6;
651 
652 				_textMan->printEndGameString(textPosX, textPosY++, kDMColorGold, curChampion->_title);
653 				for (int16 idx = kDMSkillFighter; idx <= kDMSkillWizard; idx++) {
654 					uint16 skillLevel = MIN<uint16>(16, _championMan->getSkillLevel(championIndex, idx | (kDMIgnoreObjectModifiers | kDMIgnoreTemporaryExperience)));
655 					if (skillLevel == 1)
656 						continue;
657 
658 					Common::String displStr = Common::String::format("%s %s", _inventoryMan->_skillLevelNames[skillLevel - 2], _championMan->_baseSkillName[idx]);
659 					_textMan->printEndGameString(105, textPosY = textPosY + 8, kDMColorLightestGray, displStr.c_str());
660 				}
661 				championMirrorBox._rect.top += 48;
662 				championMirrorBox._rect.bottom += 48;
663 				championPortraitBox._rect.top += 48;
664 				championPortraitBox._rect.top += 48;
665 			}
666 			_displayMan->startEndFadeToPalette(_displayMan->_paletteTopAndBottomScreen);
667 			_engineShouldQuit = true;
668 			return;
669 		}
670 T0444017:
671 		_displayMan->fillScreen(kDMColorBlack);
672 		_displayMan->blitToScreen(_displayMan->getNativeBitmapOrGraphic(kDMGraphicIdxTheEnd), &theEndBox, k40_byteWidth, kDMColorNoTransparency, 14);
673 		for (uint16 i = 0; i < 16; ++i)
674 			darkBluePalette[i] = D01_RGB_DARK_BLUE;
675 		uint16 curPalette[16];
676 		for (uint16 i = 0; i < 15; ++i)
677 			curPalette[i] = darkBluePalette[i];
678 		curPalette[15] = D09_RGB_WHITE;
679 		_displayMan->startEndFadeToPalette(curPalette);
680 		_displayMan->updateScreen();
681 		if (waitBeforeDrawingRestart)
682 			delay(300);
683 
684 		if (_restartGameAllowed) {
685 			_displayMan->_useByteBoxCoordinates = false;
686 			_displayMan->fillScreenBox(restartOuterBox, kDMColorDarkestGray);
687 			_displayMan->fillScreenBox(restartInnerBox, kDMColorBlack);
688 
689 			switch (getGameLanguage()) { // localized
690 			default:
691 			case Common::EN_ANY:
692 				_textMan->printToLogicalScreen(110, 154, kDMColorCyan, kDMColorBlack, "RESTART THIS GAME");
693 				break;
694 			case Common::DE_DEU:
695 				_textMan->printToLogicalScreen(110, 154, kDMColorCyan, kDMColorBlack, "DIESES SPIEL NEU STARTEN");
696 				break;
697 			case Common::FR_FRA:
698 				_textMan->printToLogicalScreen(110, 154, kDMColorCyan, kDMColorBlack, "RECOMMENCER CE JEU");
699 				break;
700 			}
701 
702 			curPalette[1] = D03_RGB_PINK;
703 			curPalette[4] = D09_RGB_WHITE;
704 			_eventMan->_primaryMouseInput = _eventMan->_primaryMouseInputRestartGame;
705 			_eventMan->discardAllInput();
706 			_eventMan->hideMouse();
707 			_displayMan->startEndFadeToPalette(curPalette);
708 			for (int16 verticalBlankCount = 900; --verticalBlankCount && !_restartGameRequest; delay(1))
709 				_eventMan->processCommandQueue();
710 
711 			_eventMan->showMouse();
712 			if (_restartGameRequest) {
713 				_displayMan->startEndFadeToPalette(darkBluePalette);
714 				_displayMan->fillScreen(kDMColorBlack);
715 				_displayMan->startEndFadeToPalette(_displayMan->_palDungeonView[0]);
716 				_gameMode = kDMModeLoadSavedGame;
717 				if (loadgame(1) != kDMLoadgameFailure) {
718 					startGame();
719 					_restartGameRequest = false;
720 					_eventMan->hideMouse();
721 					_eventMan->discardAllInput();
722 					return;
723 				}
724 			}
725 		}
726 
727 		_displayMan->startEndFadeToPalette(darkBluePalette);
728 	}
729 	Box box(0, 319, 0, 199);
730 	_displayMan->blitToScreen(_displayMan->getNativeBitmapOrGraphic(kDMGraphicIdxCredits), &box, k160_byteWidthScreen, kDMColorNoTransparency, k200_heightScreen);
731 
732 	_displayMan->startEndFadeToPalette(_displayMan->_palCredits);
733 	_eventMan->waitForMouseOrKeyActivity();
734 	if (_engineShouldQuit)
735 		return;
736 
737 	if (_restartGameAllowed && doNotDrawCreditsOnly) {
738 		waitBeforeDrawingRestart = false;
739 		_displayMan->startEndFadeToPalette(darkBluePalette);
740 		goto T0444017;
741 	}
742 
743 	_engineShouldQuit = true;
744 	return;
745 }
746 
747 
drawEntrance()748 void DMEngine::drawEntrance() {
749 	static Box doorsUpperHalfBox = Box(0, 231, 0, 80);
750 	static Box doorsLowerHalfBox = Box(0, 231, 81, 160);
751 	static Box closedDoorLeftBox = Box(0, 104, 30, 190);
752 	static Box closedDoorRightBox = Box(105, 231, 30, 190);
753 	/* Atari ST: { 0x000, 0x333, 0x444, 0x420, 0x654, 0x210, 0x040, 0x050, 0x432, 0x700, 0x543, 0x321, 0x222, 0x555, 0x310, 0x777 }, RGB colors are different */
754 	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
755 
756 	byte *microDungeonCurrentMapData[32];
757 
758 	_dungeonMan->_partyMapIndex = kDMMapIndexEntrance;
759 	_displayMan->_drawFloorAndCeilingRequested = true;
760 	_dungeonMan->_currMapWidth = 5;
761 	_dungeonMan->_currMapHeight = 5;
762 	_dungeonMan->_currMapData = microDungeonCurrentMapData;
763 
764 	Map map; // uninitialized, won't be used
765 	_dungeonMan->_currMap = &map;
766 	Square microDungeonSquares[25];
767 	for (uint16 i = 0; i < 25; ++i)
768 		microDungeonSquares[i] = Square(kDMElementTypeWall, 0);
769 
770 	for (int16 idx = 0; idx < 5; idx++) {
771 		microDungeonCurrentMapData[idx] = (byte*)&microDungeonSquares[idx * 5];
772 		microDungeonSquares[idx + 10] = Square(kDMElementTypeCorridor, 0);
773 	}
774 	microDungeonSquares[7] = Square(kDMElementTypeCorridor, 0);
775 	_displayMan->startEndFadeToPalette(_displayMan->_blankBuffer);
776 
777 	// note, a global variable is used here in the original
778 	_displayMan->loadIntoBitmap(kDMGraphicIdxEntrance, _displayMan->_bitmapScreen);
779 	_displayMan->drawDungeon(kDMDirSouth, 2, 0);
780 
781 	if (!_savedScreenForOpenEntranceDoors)
782 		_savedScreenForOpenEntranceDoors = new byte[k200_heightScreen * k160_byteWidthScreen * 2];
783 	memcpy(_savedScreenForOpenEntranceDoors, _displayMan->_bitmapScreen, 320 * 200);
784 
785 	_displayMan->_useByteBoxCoordinates = false;
786 	_displayMan->blitToBitmap(_displayMan->_bitmapScreen, _entranceDoorAnimSteps[8], doorsUpperHalfBox, 0, 30, k160_byteWidthScreen, k128_byteWidth, kDMColorNoTransparency, 200, 161);
787 	_displayMan->_useByteBoxCoordinates = false;
788 	_displayMan->blitToBitmap(_displayMan->_bitmapScreen, _entranceDoorAnimSteps[8], doorsLowerHalfBox, 0, 111, k160_byteWidthScreen, k128_byteWidth, kDMColorNoTransparency, 200, 161);
789 
790 	_displayMan->blitToScreen(_entranceDoorAnimSteps[0], &closedDoorLeftBox, k64_byteWidth, kDMColorNoTransparency, 161);
791 	_displayMan->blitToScreen(_entranceDoorAnimSteps[4], &closedDoorRightBox, k64_byteWidth, kDMColorNoTransparency, 161);
792 	_displayMan->startEndFadeToPalette(palEntrance);
793 }
794 
openEntranceDoors()795 void DMEngine::openEntranceDoors() {
796 	Box rightDoorBox(109, 231, 30, 193);
797 	byte *rightDoorBitmap = _displayMan->getNativeBitmapOrGraphic(kDMGraphicIdxEntranceRightDoor);
798 	Box leftDoorBox(0, 100, 30, 193);
799 	uint16 leftDoorBlitFrom = 0;
800 	byte *leftDoorBitmap = _displayMan->getNativeBitmapOrGraphic(kDMGraphicIdxEntranceLeftDoor);
801 
802 	Box screenBox(0, 319, 0, 199);
803 
804 	for (uint16 animStep = 1; animStep < 32; ++animStep) {
805 		if ((animStep % 3) == 1) {
806 			// Strangerke: CHECKME: Earlier versions of the game were using G0565_puc_Graphic535_Sound02DoorRattle instead of k02_soundDOOR_RATTLE 2
807 			_sound->play(kDMSoundIndexDoorRattle, 145, 0x40, 0x40);
808 		}
809 
810 		_displayMan->blitToScreen(_savedScreenForOpenEntranceDoors, &screenBox, 160, kDMColorNoTransparency, 200);
811 		_displayMan->blitToBitmap(leftDoorBitmap, _displayMan->_bitmapScreen, leftDoorBox, leftDoorBlitFrom, 0, 64, k160_byteWidthScreen,
812 								  kDMColorNoTransparency, 161, k200_heightScreen);
813 		_displayMan->blitToBitmap(rightDoorBitmap, _displayMan->_bitmapScreen, rightDoorBox, 0, 0, 64, k160_byteWidthScreen,
814 								  kDMColorNoTransparency, 161, k200_heightScreen);
815 		_eventMan->discardAllInput();
816 		_displayMan->updateScreen();
817 
818 		leftDoorBox._rect.right -= 4;
819 		leftDoorBlitFrom += 4;
820 		rightDoorBox._rect.left += 4;
821 
822 		delay(3);
823 	}
824 	delete[] _savedScreenForOpenEntranceDoors;
825 	_savedScreenForOpenEntranceDoors = nullptr;
826 }
827 
drawTittle()828 void DMEngine::drawTittle() {
829 	static Box boxTitleStrikesBackDestination(0, 319, 118, 174);
830 	static Box boxTitleStrikesBackSource(0, 319, 0, 56);
831 	static Box boxTitlePresents(0, 319, 90, 105);
832 	static Box boxTitleDungeonChaos(0, 319, 0, 79);
833 
834 	_displayMan->_useByteBoxCoordinates = false;
835 
836 	byte *allocatedMem = new byte[145600 * 2];
837 	byte *titleSteps = allocatedMem;
838 	byte *bitmapTitle = titleSteps;
839 	_displayMan->loadIntoBitmap(kDMGraphicIdxTitle, titleSteps);
840 
841 	titleSteps += 320 * 200;
842 	uint16 blitPalette[16];
843 	for (uint16 i = 0; i < 16; ++i)
844 		blitPalette[i] = D01_RGB_DARK_BLUE;
845 
846 	_displayMan->startEndFadeToPalette(blitPalette);
847 	_displayMan->fillScreen(kDMColorBlack);
848 	// uncomment this to draw 'Presents'
849 	//_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);
850 	blitPalette[15] = D09_RGB_WHITE;
851 	_displayMan->startEndFadeToPalette(blitPalette);
852 	byte *masterStrikesBack = titleSteps;
853 	_displayMan->blitToBitmap(bitmapTitle, masterStrikesBack, boxTitleStrikesBackSource, 0, 80, k160_byteWidthScreen, k160_byteWidthScreen, kDMColorNoTransparency, 200, 57);
854 	titleSteps += 320 * 57;
855 	byte *bitmapDungeonChaos = titleSteps; /* Unreferenced on Atari ST */
856 	_displayMan->blitToBitmap(bitmapTitle, bitmapDungeonChaos, boxTitleDungeonChaos, 0, 0, k160_byteWidthScreen, k160_byteWidthScreen, kDMColorNoTransparency, 200, 80);
857 	titleSteps += 320 * 80;
858 	bitmapTitle = bitmapDungeonChaos;
859 	uint16 destinationHeight = 12;
860 	int16 destinationPixelWidth = 48;
861 	byte *shrinkedTitle[20]; /* Only the first 18 entries are actually used */
862 	int16 blitCoordinates[20][5]; /* Only the first 18 entries are actually used */
863 	for (int16 i = 0; i < 18; i++) {
864 		shrinkedTitle[i] = titleSteps;
865 		_displayMan->blitToBitmapShrinkWithPalChange(bitmapTitle, titleSteps, 320, 80, destinationPixelWidth, destinationHeight, _displayMan->_palChangesNoChanges);
866 		blitCoordinates[i][0] = (320 - destinationPixelWidth) / 2;
867 		blitCoordinates[i][1] = blitCoordinates[i][0] + destinationPixelWidth - 1;
868 		blitCoordinates[i][2] = (160 - destinationHeight) / 2;
869 		blitCoordinates[i][3] = blitCoordinates[i][2] + destinationHeight - 1;
870 		titleSteps += (blitCoordinates[i][4] = ((destinationPixelWidth + 15) / 16) * 8) * destinationHeight * 2;
871 		destinationHeight += 4;
872 		destinationPixelWidth += 16;
873 	}
874 	blitPalette[15] = D01_RGB_DARK_BLUE;
875 	_displayMan->startEndFadeToPalette(blitPalette);
876 	_displayMan->fillScreen(kDMColorBlack);
877 	blitPalette[3] = D05_RGB_DARK_GOLD;
878 	blitPalette[4] = D02_RGB_LIGHT_BROWN;
879 	blitPalette[5] = D06_RGB_GOLD;
880 	blitPalette[6] = D04_RGB_LIGHTER_BROWN;
881 	blitPalette[8] = D08_RGB_YELLOW;
882 	blitPalette[15] = D07_RGB_RED;
883 	blitPalette[10] = D01_RGB_DARK_BLUE;
884 	blitPalette[12] = D01_RGB_DARK_BLUE;
885 	_displayMan->startEndFadeToPalette(blitPalette);
886 	delay(1);
887 	for (int16 i = 0; i < 18; i++) {
888 		delay(2);
889 		Box box(blitCoordinates[i][0], blitCoordinates[i][1], blitCoordinates[i][2], blitCoordinates[i][3]);
890 		_displayMan->blitToBitmap(shrinkedTitle[i], _displayMan->_bitmapScreen, box, 0, 0, blitCoordinates[i][4], k160_byteWidthScreen, kDMColorNoTransparency, blitCoordinates[i][3] - blitCoordinates[i][2] + 1, k200_heightScreen);
891 	}
892 	delay(25);
893 	_displayMan->blitToBitmap(masterStrikesBack, _displayMan->_bitmapScreen, boxTitleStrikesBackDestination, 0, 0, k160_byteWidthScreen, k160_byteWidthScreen, kDMColorBlack, 57, k200_heightScreen);
894 	blitPalette[10] = D00_RGB_BLACK;
895 	blitPalette[12] = D07_RGB_RED;
896 	_displayMan->startEndFadeToPalette(blitPalette);
897 	delete[] allocatedMem;
898 	delay(75);
899 }
900 
entranceDrawCredits()901 void DMEngine::entranceDrawCredits() {
902 	_eventMan->showMouse();
903 	_displayMan->startEndFadeToPalette(_displayMan->_blankBuffer);
904 	_displayMan->loadIntoBitmap(kDMGraphicIdxCredits, _displayMan->_bitmapScreen);
905 	_displayMan->startEndFadeToPalette(_displayMan->_palCredits);
906 	delay(50);
907 	_eventMan->waitForMouseOrKeyActivity();
908 	_gameMode = kDMModeEntranceDrawCredits;
909 }
910 
fuseSequence()911 void DMEngine::fuseSequence() {
912 	_gameWon = true;
913 	if (_inventoryMan->_inventoryChampionOrdinal)
914 		_inventoryMan->toggleInventory(kDMChampionCloseInventory);
915 
916 	_eventMan->highlightBoxDisable();
917 	_championMan->_party._magicalLightAmount = 200;
918 	_inventoryMan->setDungeonViewPalette();
919 	_championMan->_party._fireShieldDefense = _championMan->_party._spellShieldDefense = _championMan->_party._shieldDefense = 100;
920 	_timeline->refreshAllChampionStatusBoxes();
921 	fuseSequenceUpdate();
922 	int16 lordChaosMapX = _dungeonMan->_partyMapX;
923 	int16 lordChaosMapY = _dungeonMan->_partyMapY;
924 	lordChaosMapX += _dirIntoStepCountEast[_dungeonMan->_partyDir];
925 	lordChaosMapY += _dirIntoStepCountNorth[_dungeonMan->_partyDir];
926 	Thing lordChaosThing = _groupMan->groupGetThing(lordChaosMapX, lordChaosMapY);
927 	Group *lordGroup = (Group*)_dungeonMan->getThingData(lordChaosThing);
928 	lordGroup->_health[0] = 10000;
929 	_dungeonMan->setGroupCells(lordGroup, kDMCreatureTypeSingleCenteredCreature, _dungeonMan->_partyMapIndex);
930 	_dungeonMan->setGroupDirections(lordGroup, returnOppositeDir(_dungeonMan->_partyDir), _dungeonMan->_partyMapIndex);
931 
932 	bool removeFluxcagesFromLordChaosSquare = true;
933 	int16 fluxCageMapX = _dungeonMan->_partyMapX;
934 	int16 fluxcageMapY = _dungeonMan->_partyMapY;
935 
936 	for (;;) {
937 		Thing curThing = _dungeonMan->getSquareFirstObject(fluxCageMapX, fluxcageMapY);
938 		while (curThing != _thingEndOfList) {
939 			if (curThing.getType() == kDMThingTypeExplosion) {
940 				Explosion *curExplosion = (Explosion*)_dungeonMan->getThingData(curThing);
941 				if (curExplosion->getType() == kDMExplosionTypeFluxcage) {
942 					_dungeonMan->unlinkThingFromList(curThing, Thing(0), fluxCageMapX, fluxcageMapY);
943 					curExplosion->setNextThing(_thingNone);
944 					continue;
945 				}
946 			}
947 			curThing = _dungeonMan->getNextThing(curThing);
948 		}
949 		if (removeFluxcagesFromLordChaosSquare) {
950 			removeFluxcagesFromLordChaosSquare = false;
951 			fluxCageMapX = lordChaosMapX;
952 			fluxcageMapY = lordChaosMapY;
953 		} else
954 			break;
955 	}
956 	fuseSequenceUpdate();
957 	for (int16 attackId = 55; attackId <= 255; attackId += 40) {
958 		_projexpl->createExplosion(_thingExplFireBall, attackId, lordChaosMapX, lordChaosMapY, kDMCreatureTypeSingleCenteredCreature);
959 		fuseSequenceUpdate();
960 	}
961 	_sound->requestPlay(kDMSoundIndexBuzz, lordChaosMapX, lordChaosMapY, kDMSoundModePlayIfPrioritized);
962 	lordGroup->_type = kDMCreatureTypeLordOrder;
963 	fuseSequenceUpdate();
964 	for (int16 attackId = 55; attackId <= 255; attackId += 40) {
965 		_projexpl->createExplosion(_thingExplHarmNonMaterial, attackId, lordChaosMapX, lordChaosMapY, kDMCreatureTypeSingleCenteredCreature);
966 		fuseSequenceUpdate();
967 	}
968 	for (int16 cycleCount = 3; cycleCount > 0; cycleCount--) {
969 		for (int16 switchCount = 4; switchCount > 0; switchCount--) {
970 			_sound->requestPlay(kDMSoundIndexBuzz, lordChaosMapX, lordChaosMapY, kDMSoundModePlayIfPrioritized);
971 			lordGroup->_type = (switchCount & 0x0001) ? kDMCreatureTypeLordOrder : kDMCreatureTypeLordChaos;
972 			for (int16 fuseSequenceUpdateCount = cycleCount - 1; fuseSequenceUpdateCount >= 0; fuseSequenceUpdateCount--)
973 				fuseSequenceUpdate();
974 		}
975 	}
976 	_projexpl->createExplosion(_thingExplFireBall, 255, lordChaosMapX, lordChaosMapY, kDMCreatureTypeSingleCenteredCreature);
977 	_projexpl->createExplosion(_thingExplHarmNonMaterial, 255, lordChaosMapX, lordChaosMapY, kDMCreatureTypeSingleCenteredCreature);
978 	fuseSequenceUpdate();
979 	lordGroup->_type = kDMCreatureTypeGreyLord;
980 	fuseSequenceUpdate();
981 	_displayMan->_doNotDrawFluxcagesDuringEndgame = true;
982 	fuseSequenceUpdate();
983 	for (int16 curMapX = 0; curMapX < _dungeonMan->_currMapWidth; curMapX++) {
984 		for (int curMapY = 0; curMapY < _dungeonMan->_currMapHeight; curMapY++) {
985 			Thing curThing = _groupMan->groupGetThing(curMapX, curMapY);
986 			if ((curThing != _thingEndOfList) && ((curMapX != lordChaosMapX) || (curMapY != lordChaosMapY))) {
987 				_groupMan->groupDelete(curMapX, curMapY);
988 			}
989 		}
990 	}
991 	fuseSequenceUpdate();
992 	/* 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) */
993 	Thing curThing = _dungeonMan->getSquareFirstThing(0, 0);
994 	int16 textStringThingCount = 0;
995 	Thing textStringThings[8];
996 	while (curThing != _thingEndOfList) {
997 		if (curThing.getType() == kDMstringTypeText)
998 			textStringThings[textStringThingCount++] = curThing;
999 
1000 		curThing = _dungeonMan->getNextThing(curThing);
1001 	}
1002 	char textFirstChar = 'A';
1003 	int16 maxCount = textStringThingCount;
1004 	while (textStringThingCount--) {
1005 		for (int16 idx = 0; idx < maxCount; idx++) {
1006 			char decodedString[200];
1007 			_dungeonMan->decodeText(decodedString, textStringThings[idx], (TextType)(kDMTextTypeMessage | kDMMaskDecodeEvenIfInvisible));
1008 			if (decodedString[1] == textFirstChar) {
1009 				_textMan->clearAllRows();
1010 				decodedString[1] = '\n'; /* New line */
1011 				_textMan->printMessage(kDMColorWhite, &decodedString[1]);
1012 				fuseSequenceUpdate();
1013 				delay(780);
1014 				textFirstChar++;
1015 				break;
1016 			}
1017 		}
1018 	}
1019 
1020 	for (int16 attackId = 55; attackId <= 255; attackId += 40) {
1021 		_projexpl->createExplosion(_thingExplHarmNonMaterial, attackId, lordChaosMapX, lordChaosMapY, kDMCreatureTypeSingleCenteredCreature);
1022 		fuseSequenceUpdate();
1023 	}
1024 
1025 	delay(600);
1026 	_restartGameAllowed = false;
1027 	endGame(true);
1028 }
1029 
fuseSequenceUpdate()1030 void DMEngine::fuseSequenceUpdate() {
1031 	_timeline->processTimeline();
1032 	_displayMan->drawDungeon(_dungeonMan->_partyDir, _dungeonMan->_partyMapX, _dungeonMan->_partyMapY);
1033 	_sound->playPendingSound();
1034 	_eventMan->discardAllInput();
1035 	_displayMan->updateScreen();
1036 	delay(2);
1037 	_gameTime++; /* BUG0_71 Some timings are too short on fast computers.
1038 					  The ending animation when Lord Chaos is fused plays too quickly because the execution speed is not limited */
1039 }
1040 
getGameLanguage()1041 Common::Language DMEngine::getGameLanguage() {
1042 	return _gameVersion->_desc.language;
1043 }
1044 
1045 } // End of namespace DM
1046