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 #include "common/scummsys.h"
24 #include "common/config-manager.h"
25 #include "common/memstream.h"
26 #include "common/serializer.h"
27 #include "graphics/palette.h"
28 #include "graphics/scaler.h"
29 #include "graphics/thumbnail.h"
30 #include "mads/mads.h"
31 #include "mads/compression.h"
32 #include "mads/game.h"
33 #include "mads/game_data.h"
34 #include "mads/events.h"
35 #include "mads/screen.h"
36 #include "mads/msurface.h"
37 #include "mads/resources.h"
38 #include "mads/dragonsphere/game_dragonsphere.h"
39 #include "mads/nebular/game_nebular.h"
40 #include "mads/phantom/game_phantom.h"
41 
42 namespace MADS {
43 
init(MADSEngine * vm)44 Game *Game::init(MADSEngine *vm) {
45 	switch (vm->getGameID()) {
46 	case GType_RexNebular:
47 		return new Nebular::GameNebular(vm);
48 	case GType_Dragonsphere:
49 		return new Dragonsphere::GameDragonsphere(vm);
50 	case GType_Phantom:
51 		return new Phantom::GamePhantom(vm);
52 	default:
53 		error("Game: Unknown game");
54 	}
55 
56 	return nullptr;
57 }
58 
Game(MADSEngine * vm)59 Game::Game(MADSEngine *vm)
60 	: _vm(vm), _surface(nullptr), _objects(vm), _scene(vm), _screenObjects(vm), _player(vm), _camX(vm), _camY(vm) {
61 	_sectionNumber = 1;
62 	_priorSectionNumber = 0;
63 	_loadGameSlot = -1;
64 	_lastSave = -1;
65 	_saveFile = nullptr;
66 	_saveThumb = nullptr;
67 	_statusFlag = 0;
68 	_sectionHandler = nullptr;
69 	_sectionNumber = 1;
70 	_priorSectionNumber = 0;
71 	_currentSectionNumber = -1;
72 	_kernelMode = KERNEL_GAME_LOAD;
73 	_quoteEmergency = false;
74 	_vocabEmergency = false;
75 	_aaName = "*I0.AA";
76 	_priorFrameTimer = 0;
77 	_anyEmergency = false;
78 	_triggerMode = SEQUENCE_TRIGGER_PARSER;
79 	_triggerSetupMode = SEQUENCE_TRIGGER_DAEMON;
80 	_trigger = 0;
81 	_winStatus = 0;
82 	_widepipeCtr = 0;
83 	_fx = kTransitionNone;
84 	_panningSpeed = 1; // Medium speed
85 
86 	// Load the inventory object list
87 	_objects.load();
88 	if (_objects._inventoryList.size() > 0)
89 		// At least one item in default inventory, so select first item for display
90 		_scene._userInterface._selectedInvIndex = 0;
91 
92 	// Load the quotes
93 	loadQuotes();
94 }
95 
~Game()96 Game::~Game() {
97 	if (_saveThumb) {
98 		_saveThumb->free();
99 		delete _saveThumb;
100 	}
101 
102 	delete _saveFile;
103 	_surface->free();
104 	delete _surface;
105 	delete _sectionHandler;
106 }
107 
run()108 void Game::run() {
109 	// If requested, load a savegame instead of showing the intro
110 	if (ConfMan.hasKey("save_slot")) {
111 		int saveSlot = ConfMan.getInt("save_slot");
112 		if (saveSlot >= 0 && saveSlot <= 999)
113 			_loadGameSlot = saveSlot;
114 	}
115 
116 	_statusFlag = true;
117 
118 	while (!_vm->shouldQuit()) {
119 		if (_loadGameSlot == -1) {
120 			startGame();
121 		}
122 
123 		// Get the initial starting time for the first scene
124 		_scene._frameStartTime = _vm->_events->getFrameCounter();
125 
126 		if (!_vm->shouldQuit())
127 			gameLoop();
128 	}
129 }
130 
splitQuote(const Common::String & source,Common::String & line1,Common::String & line2)131 void Game::splitQuote(const Common::String &source, Common::String &line1, Common::String &line2) {
132 	// Make the first line up the end of the word at the half-way point
133 	const char *strP = source.c_str() + source.size() / 2;
134 	while (*strP != ' ') ++strP;
135 
136 	line1 = Common::String(source.c_str(), strP);
137 
138 	// The rest of the string goes in the second line
139 	while (*strP == ' ') ++strP;
140 	line2 = Common::String(strP);
141 }
142 
gameLoop()143 void Game::gameLoop() {
144 	while (!_vm->shouldQuit() && _statusFlag && !_winStatus) {
145 		if (_loadGameSlot != -1) {
146 			loadGame(_loadGameSlot);
147 			_loadGameSlot = -1;
148 		}
149 
150 		setSectionHandler();
151 		_sectionHandler->preLoadSection();
152 		initSection(_sectionNumber);
153 		_vm->_sound->init(_sectionNumber);
154 		_sectionHandler->postLoadSection();
155 
156 		_scene._spriteSlots.reset();
157 
158 		if (_sectionNumber == _currentSectionNumber)
159 			sectionLoop();
160 
161 		_player.releasePlayerSprites();
162 		assert(_scene._sprites.size() == 0);
163 
164 		_vm->_palette->unlock();
165 		_vm->_events->waitCursor();
166 		_vm->_events->freeCursors();
167 		_vm->_sound->closeDriver();
168 	}
169 }
170 
sectionLoop()171 void Game::sectionLoop() {
172 	while (!_vm->shouldQuit() && _statusFlag && !_winStatus &&
173 			(_sectionNumber == _currentSectionNumber)) {
174 		_kernelMode = KERNEL_ROOM_PRELOAD;
175 		_player._spritesChanged = true;
176 		_quoteEmergency = false;
177 		_vocabEmergency = false;
178 		_vm->_events->waitCursor();
179 
180 		_scene.clearVocab();
181 		_scene._dynamicHotspots.clear();
182 		_scene.loadSceneLogic();
183 
184 		_player._walkAnywhere = false;
185 		_player._stepEnabled = true;
186 		_player._visible = true;
187 		_vm->_dialogs->_defaultPosition = Common::Point(-1, -1);
188 		_visitedScenes.add(_scene._nextSceneId);
189 
190 		// Reset the user interface
191 		_screenObjects._forceRescan = true;
192 		_screenObjects._inputMode = kInputBuildingSentences;
193 		_scene._userInterface._scrollbarActive = SCROLLBAR_NONE;
194 
195 		_player._loadsFirst = true;
196 
197 		_scene._sceneLogic->setup();
198 		if (_player._spritesChanged || _player._loadsFirst) {
199 			if (_player._spritesLoaded)
200 				_player.releasePlayerSprites();
201 			_vm->_palette->resetGamePalette(18, 10);
202 			_scene._spriteSlots.reset();
203 		} else {
204 			_vm->_palette->initPalette();
205 		}
206 
207 		// Set up scene palette usage
208 		_scene._scenePaletteUsage.clear();
209 		_scene._scenePaletteUsage.push_back(PaletteUsage::UsageEntry(0xF0));
210 		_scene._scenePaletteUsage.push_back(PaletteUsage::UsageEntry(0xF1));
211 		_scene._scenePaletteUsage.push_back(PaletteUsage::UsageEntry(0xF2));
212 		_vm->_palette->_paletteUsage.load(&_scene._scenePaletteUsage);
213 
214 		if (!_player._spritesLoaded && _player._loadsFirst) {
215 			if (_player.loadSprites(""))
216 				_vm->quitGame();
217 			_player._loadedFirst = true;
218 		}
219 
220 		_scene.loadScene(_scene._nextSceneId, _aaName, 0);
221 		camInitDefault();
222 		camSetSpeed();
223 
224 
225 		_vm->_sound->pauseNewCommands();
226 
227 		if (!_player._spritesLoaded) {
228 			if (_player.loadSprites(""))
229 				_vm->quitGame();
230 			_player._loadedFirst = false;
231 		}
232 
233 		_vm->_events->initVars();
234 		_scene._userInterface._highlightedCommandIndex = -1;
235 		_scene._userInterface._highlightedInvIndex = -1;
236 		_scene._userInterface._highlightedItemVocabIndex = -1;
237 
238 		_scene._action.clear();
239 		_player.setFinalFacing();
240 		_player._facing = _player._turnToFacing;
241 		_player.cancelCommand();
242 		_kernelMode = KERNEL_ROOM_INIT;
243 
244 		switch (_vm->_screenFade) {
245 		case SCREEN_FADE_SMOOTH:
246 			_fx = kTransitionFadeOutIn;
247 			break;
248 		case SCREEN_FADE_FAST:
249 			_fx = kNullPaletteCopy;
250 			break;
251 		default:
252 			_fx = kTransitionNone;
253 			break;
254 		}
255 
256 		_trigger = 0;
257 		_priorFrameTimer = _scene._frameStartTime;
258 
259 		// If in the middle of restoring a game, handle the rest of the loading
260 		if (_saveFile != nullptr) {
261 			Common::Serializer s(_saveFile, nullptr);
262 			synchronize(s, false);
263 			delete _saveFile;
264 			_saveFile = nullptr;
265 		}
266 
267 		// Call the scene logic for entering the given scene
268 		_triggerSetupMode = SEQUENCE_TRIGGER_DAEMON;
269 		_scene._sceneLogic->enter();
270 
271 		// Set player data
272 		_player._targetPos = _player._playerPos;
273 		_player._turnToFacing = _player._facing;
274 		_player._targetFacing = _player._facing;
275 		_player.selectSeries();
276 		_player.updateFrame();
277 
278 		_player._beenVisible = _player._visible;
279 		_player._special = _scene.getDepthHighBits(_player._playerPos);
280 		_player._priorTimer = _scene._frameStartTime - _player._ticksAmount;
281 		_player.idle();
282 
283 		if (_scene._userInterface._selectedInvIndex >= 0) {
284 			_scene._userInterface.loadInventoryAnim(
285 				_objects._inventoryList[_scene._userInterface._selectedInvIndex]);
286 		} else {
287 			_scene._userInterface.noInventoryAnim();
288 		}
289 
290 		_kernelMode = KERNEL_ACTIVE_CODE;
291 		_scene._roomChanged = false;
292 
293 		if ((_quoteEmergency || _vocabEmergency) && !_anyEmergency) {
294 			_scene._currentSceneId = _scene._priorSceneId;
295 			_anyEmergency = true;
296 		} else {
297 			_anyEmergency = false;
298 			_scene.loop();
299 		}
300 
301 		_vm->_events->waitCursor();
302 		_kernelMode = KERNEL_ROOM_PRELOAD;
303 
304 		for (int i = 0; i < 10; i++) {
305 			delete _scene._animation[i];
306 			_scene._animation[i] = nullptr;
307 		}
308 
309 		_scene._reloadSceneFlag = false;
310 
311 		_scene._userInterface.noInventoryAnim();
312 		_scene.removeSprites();
313 
314 		if (!_player._loadedFirst) {
315 			_player._spritesLoaded = false;
316 			_player._spritesChanged = true;
317 		}
318 
319 		// Clear the scene
320 		_scene.freeCurrentScene();
321 		_sectionNumber = _scene._nextSceneId / 100;
322 
323 		// Check whether to show a dialog
324 		checkShowDialog();
325 	}
326 }
327 
initSection(int sectionNumber)328 void Game::initSection(int sectionNumber) {
329 	_priorSectionNumber = _currentSectionNumber;
330 	_currentSectionNumber = sectionNumber;
331 
332 	_vm->_palette->resetGamePalette(18, 10);
333 	_vm->_palette->setLowRange();
334 
335 	if (_scene._mode == SCREENMODE_VGA)
336 		_vm->_palette->setPalette(_vm->_palette->_mainPalette, 0, 4);
337 
338 	_vm->_events->loadCursors("*CURSOR.SS");
339 
340 	assert(_vm->_events->_cursorSprites);
341 	_vm->_events->setCursor2((_vm->_events->_cursorSprites->getCount() <= 1) ?
342 		CURSOR_ARROW : CURSOR_WAIT);
343 }
344 
loadQuotes()345 void Game::loadQuotes() {
346 	File f("*QUOTES.DAT");
347 
348 	Common::String msg;
349 	while (true) {
350 		uint8 b = f.readByte();
351 
352 		msg += b;
353 		if (f.eos() || b == '\0') {
354 			// end of string, add it to the strings list
355 			_quotes.push_back(msg);
356 			msg = "";
357 		}
358 
359 		if (f.eos()) break;
360 	}
361 
362 	f.close();
363 }
364 
getMessage(uint32 id)365 Common::StringArray Game::getMessage(uint32 id) {
366 	File f("*MESSAGES.DAT");
367 	int count = f.readUint16LE();
368 
369 	for (int idx = 0; idx < count; ++idx) {
370 		uint32 itemId = f.readUint32LE();
371 		uint32 offset = f.readUint32LE();
372 		uint16 size = f.readUint16LE();
373 
374 		if (itemId == id) {
375 			// Get the source buffer size
376 			uint16 sizeIn;
377 			if (idx == (count - 1)) {
378 				sizeIn = f.size() - offset;
379 			} else {
380 				f.skip(4);
381 				uint32 nextOffset = f.readUint32LE();
382 				sizeIn = nextOffset - offset;
383 			}
384 
385 			// Get the compressed data
386 			f.seek(offset);
387 			byte *bufferIn = new byte[sizeIn];
388 			f.read(bufferIn, sizeIn);
389 
390 			// Decompress it
391 			char *bufferOut = new char[size];
392 			FabDecompressor fab;
393 			fab.decompress(bufferIn, sizeIn, (byte *)bufferOut, size);
394 
395 			// Form the output string list
396 			Common::StringArray result;
397 			const char *p = bufferOut;
398 			while (p < (bufferOut + size)) {
399 				result.push_back(p);
400 				p += strlen(p) + 1;
401 			}
402 
403 			delete[] bufferIn;
404 			delete[] bufferOut;
405 			return result;
406 		}
407 	}
408 
409 	error("Invalid message Id specified");
410 }
411 
412 static const char *const DEBUG_STRING = "WIDEPIPE";
413 
handleKeypress(const Common::KeyState & kbd)414 void Game::handleKeypress(const Common::KeyState &kbd) {
415 	if (kbd.flags & Common::KBD_CTRL) {
416 		if (_widepipeCtr == 8) {
417 			// Implement original game cheating keys here someday
418 		} else {
419 			if (kbd.keycode == (Common::KEYCODE_a +
420 					(DEBUG_STRING[_widepipeCtr] - 'a'))) {
421 				if (++_widepipeCtr == 8) {
422 					MessageDialog *dlg = new MessageDialog(_vm, 2,
423 						"CHEATING ENABLED", "(for your convenience).");
424 					dlg->show();
425 					delete dlg;
426 				}
427 			}
428 		}
429 	}
430 
431 	Scene &scene = _vm->_game->_scene;
432 	switch (kbd.keycode) {
433 	case Common::KEYCODE_F1:
434 		_vm->_dialogs->_pendingDialog = DIALOG_GAME_MENU;
435 		break;
436 	case Common::KEYCODE_F5:
437 		_vm->_dialogs->_pendingDialog = DIALOG_SAVE;
438 		break;
439 	case Common::KEYCODE_F7:
440 		_vm->_dialogs->_pendingDialog = DIALOG_RESTORE;
441 		break;
442 	case Common::KEYCODE_PAGEUP:
443 		scene._userInterface._scrollbarStrokeType = SCROLLBAR_UP;
444 		scene._userInterface.changeScrollBar();
445 		break;
446 	case Common::KEYCODE_PAGEDOWN:
447 		scene._userInterface._scrollbarStrokeType = SCROLLBAR_DOWN;
448 		scene._userInterface.changeScrollBar();
449 		break;
450 
451 
452 	default:
453 		break;
454 	}
455 }
456 
synchronize(Common::Serializer & s,bool phase1)457 void Game::synchronize(Common::Serializer &s, bool phase1) {
458 	if (phase1) {
459 		s.syncAsSint16LE(_fx);
460 		s.syncAsSint16LE(_trigger);
461 		s.syncAsUint16LE(_triggerSetupMode);
462 		s.syncAsUint16LE(_triggerMode);
463 		s.syncString(_aaName);
464 		s.syncAsSint16LE(_lastSave);
465 
466 		_scene.synchronize(s);
467 		_objects.synchronize(s);
468 		_visitedScenes.synchronize(s, _scene._nextSceneId);
469 		_player.synchronize(s);
470 		_screenObjects.synchronize(s);
471 	} else {
472 		// Load scene specific data for the loaded scene
473 		_scene._sceneLogic->synchronize(s);
474 	}
475 }
476 
loadGame(int slotNumber)477 void Game::loadGame(int slotNumber) {
478 	_saveFile = g_system->getSavefileManager()->openForLoading(
479 		_vm->generateSaveName(slotNumber));
480 
481 	Common::Serializer s(_saveFile, nullptr);
482 
483 	// Load the savaegame header
484 	MADSSavegameHeader header;
485 	if (!readSavegameHeader(_saveFile, header))
486 		error("Invalid savegame");
487 
488 	// Load most of the savegame data with the exception of scene specific info
489 	synchronize(s, true);
490 
491 	// Set up section/scene and other initial states for post-load
492 	_currentSectionNumber = -2;
493 	_scene._currentSceneId = -2;
494 	_sectionNumber = _scene._nextSceneId / 100;
495 	_scene._frameStartTime = _vm->_events->getFrameCounter();
496 	_vm->_screen->_shakeCountdown = -1;
497 
498 	// Default the selected inventory item to the first one, if the player has any
499 	_scene._userInterface._selectedInvIndex = _objects._inventoryList.size() > 0 ? 0 : -1;
500 
501 	// Set player sprites sets flags
502 	_player._spritesLoaded = false;
503 	_player._spritesChanged = true;
504 }
505 
saveGame(int slotNumber,const Common::String & saveName)506 void Game::saveGame(int slotNumber, const Common::String &saveName) {
507 	Common::OutSaveFile *out = g_system->getSavefileManager()->openForSaving(
508 		_vm->generateSaveName(slotNumber));
509 
510 	MADSSavegameHeader header;
511 	header._saveName = saveName;
512 	writeSavegameHeader(out, header);
513 
514 	Common::Serializer s(nullptr, out);
515 	synchronize(s, true);
516 	synchronize(s, false);
517 
518 	out->finalize();
519 	delete out;
520 }
521 
522 const char *const SAVEGAME_STR = "MADS";
523 #define SAVEGAME_STR_SIZE 4
524 
readSavegameHeader(Common::InSaveFile * in,MADSSavegameHeader & header,bool skipThumbnail)525 WARN_UNUSED_RESULT bool Game::readSavegameHeader(Common::InSaveFile *in, MADSSavegameHeader &header, bool skipThumbnail) {
526 	char saveIdentBuffer[SAVEGAME_STR_SIZE + 1];
527 
528 	// Validate the header Id
529 	in->read(saveIdentBuffer, SAVEGAME_STR_SIZE + 1);
530 	if (strncmp(saveIdentBuffer, SAVEGAME_STR, SAVEGAME_STR_SIZE))
531 		return false;
532 
533 	header._version = in->readByte();
534 	if (header._version > MADS_SAVEGAME_VERSION)
535 		return false;
536 
537 	// Read in the string
538 	header._saveName.clear();
539 	char ch;
540 	while ((ch = (char)in->readByte()) != '\0') header._saveName += ch;
541 
542 	// Get the thumbnail
543 	if (!Graphics::loadThumbnail(*in, header._thumbnail, skipThumbnail)) {
544 		return false;
545 	}
546 
547 	// Read in save date/time
548 	header._year = in->readSint16LE();
549 	header._month = in->readSint16LE();
550 	header._day = in->readSint16LE();
551 	header._hour = in->readSint16LE();
552 	header._minute = in->readSint16LE();
553 	header._totalFrames = in->readUint32LE();
554 
555 	return true;
556 }
557 
writeSavegameHeader(Common::OutSaveFile * out,MADSSavegameHeader & header)558 void Game::writeSavegameHeader(Common::OutSaveFile *out, MADSSavegameHeader &header) {
559 	// Write out a savegame header
560 	out->write(SAVEGAME_STR, SAVEGAME_STR_SIZE + 1);
561 
562 	out->writeByte(MADS_SAVEGAME_VERSION);
563 
564 	// Write savegame name
565 	out->write(header._saveName.c_str(), header._saveName.size());
566 	out->writeByte('\0');
567 
568 	// Handle the thumbnail. If there's already one set by the game, create one
569 	if (!_saveThumb)
570 		createThumbnail();
571 	Graphics::saveThumbnail(*out, *_saveThumb);
572 
573 	_saveThumb->free();
574 	delete _saveThumb;
575 	_saveThumb = nullptr;
576 
577 	// Write out the save date/time
578 	TimeDate td;
579 	g_system->getTimeAndDate(td);
580 	out->writeSint16LE(td.tm_year + 1900);
581 	out->writeSint16LE(td.tm_mon + 1);
582 	out->writeSint16LE(td.tm_mday);
583 	out->writeSint16LE(td.tm_hour);
584 	out->writeSint16LE(td.tm_min);
585 	out->writeUint32LE(_vm->_events->getFrameCounter());
586 }
587 
createThumbnail()588 void Game::createThumbnail() {
589 	if (_saveThumb) {
590 		_saveThumb->free();
591 		delete _saveThumb;
592 	}
593 
594 	uint8 thumbPalette[PALETTE_SIZE];
595 	_vm->_palette->grabPalette(thumbPalette, 0, PALETTE_COUNT);
596 	_saveThumb = new Graphics::Surface();
597 	::createThumbnail(_saveThumb, (const byte *)_vm->_screen->getPixels(),
598 		MADS_SCREEN_WIDTH, MADS_SCREEN_HEIGHT, thumbPalette);
599 }
600 
syncTimers(SyncType slaveType,int slaveId,SyncType masterType,int masterId)601 void Game::syncTimers(SyncType slaveType, int slaveId, SyncType masterType, int masterId) {
602 	uint32 syncTime = 0;
603 
604 	switch (masterType) {
605 	case SYNC_SEQ:
606 		syncTime = _scene._sequences[masterId]._timeout;
607 		break;
608 
609 	case SYNC_ANIM:
610 		syncTime = _scene._animation[masterId]->getNextFrameTimer();
611 		break;
612 
613 	case SYNC_CLOCK:
614 		syncTime = _scene._frameStartTime + masterId;
615 		break;
616 
617 	case SYNC_PLAYER:
618 		syncTime = _player._priorTimer;
619 		break;
620 	}
621 
622 
623 	switch (slaveType) {
624 	case SYNC_SEQ:
625 		_scene._sequences[slaveId]._timeout = syncTime;
626 		break;
627 
628 	case SYNC_PLAYER:
629 		_player._priorTimer = syncTime;
630 		break;
631 
632 	case SYNC_ANIM:
633 		_scene._animation[slaveId]->setNextFrameTimer(syncTime);
634 		break;
635 
636 	case SYNC_CLOCK:
637 		error("syncTimer is trying to force _frameStartTime");
638 	}
639 }
640 
camInitDefault()641 void Game::camInitDefault() {
642 	_camX.setDefaultPanX();
643 	_camY.setDefaultPanY();
644 }
645 
camSetSpeed()646 void Game::camSetSpeed() {
647 	switch (_panningSpeed) {
648 	case 1:
649 		_camX._speed = 8;
650 		_camY._speed = 4;
651 		break;
652 
653 	case 2:
654 		_camX._speed = 320;
655 		_camY._speed = 160;
656 		break;
657 
658 	default:
659 		_camX._speed = 4;
660 		_camY._speed = 2;
661 		break;
662 	}
663 }
664 
camUpdate()665 void Game::camUpdate() {
666 	bool any_pan = _camX.camPan(&_scene._posAdjust.x, &_player._playerPos.x, 320, _scene._sceneInfo->_width);
667 	any_pan |= _camY.camPan(&_scene._posAdjust.y, &_player._playerPos.y, 156, _scene._sceneInfo->_height);
668 
669 	if (any_pan) {
670 		_scene.setCamera(_scene._posAdjust);
671 		_screenObjects._forceRescan = true;
672 	}
673 }
674 
675 } // End of namespace MADS
676