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 "base/plugins.h"
24 
25 #include "common/archive.h"
26 #include "common/config-manager.h"
27 #include "common/debug-channels.h"
28 #include "audio/mixer.h"
29 
30 #include "engines/util.h"
31 #include "graphics/surface.h"
32 
33 #include "ngi/ngi.h"
34 #include "ngi/detection.h"
35 #include "ngi/gameloader.h"
36 #include "ngi/messages.h"
37 #include "ngi/behavior.h"
38 #include "ngi/modal.h"
39 #include "ngi/input.h"
40 #include "ngi/motion.h"
41 #include "ngi/statics.h"
42 #include "ngi/scenes.h"
43 #include "ngi/floaters.h"
44 #include "ngi/console.h"
45 #include "ngi/constants.h"
46 
47 namespace NGI {
48 
49 NGIEngine *g_nmi = nullptr;
50 Vars *g_vars = nullptr;
51 
NGIEngine(OSystem * syst,const NGIGameDescription * gameDesc)52 NGIEngine::NGIEngine(OSystem *syst, const NGIGameDescription *gameDesc) :
53 	Engine(syst),
54 	_gameDescription(gameDesc),
55 	_rnd("ngi"),
56 	_gameProject(nullptr),
57 	_modalObject(nullptr),
58 	_currSoundList1(),
59 	_mapTable() {
60 
61 	// Setup mixer
62 	if (!_mixer->isReady()) {
63 		warning("Sound initialization failed.");
64 	}
65 
66 	syncSoundSettings();
67 	_sfxVolume = ConfMan.getInt("sfx_volume") * 39 - 10000;
68 	_musicVolume = ConfMan.getInt("music_volume");
69 
70 	setDebugger(new Console(this));
71 
72 	_gameProjectVersion = 0;
73 	_pictureScale = 8;
74 	_scrollSpeed = 0;
75 	_currSoundListCount = 0;
76 
77 	_updateTicks = 0;
78 	_lastInputTicks = 0;
79 	_lastButtonUpTicks = 0;
80 
81 	_currArchive = 0;
82 
83 	_soundEnabled = true;
84 	_flgSoundList = true;
85 
86 	_inputController = 0;
87 	_inputDisabled = false;
88 
89 	_normalSpeed = true;
90 
91 	_currentCheat = -1;
92 	_currentCheatPos = 0;
93 
94 	_liftEnterMQ = 0;
95 	_liftExitMQ = 0;
96 	_lift = 0;
97 	_lastLiftButton = nullptr;
98 	_liftX = 0;
99 	_liftY = 0;
100 
101 	_gameContinue = true;
102 	_needRestart = false;
103 	_flgPlayIntro = true;
104 	_gamePaused = false;
105 	_inputArFlag = false;
106 	_recordEvents = false;
107 	_mainMenu_debugEnabled = false;
108 
109 	_flgGameIsRunning = true;
110 
111 	_isProcessingMessages = false;
112 
113 	_musicAllowed = -1;
114 	_musicGameVar = 0;
115 	_musicMinDelay = 0;
116 	_musicMaxDelay = 0;
117 	_musicLocal = 0;
118 	_trackStartDelay = 0;
119 
120 	_stream2playing = false;
121 
122 	_numSceneTracks = 0;
123 	_sceneTrackHasSequence = false;
124 	_sceneTrackIsPlaying = false;
125 
126 	_aniMan = nullptr;
127 	_aniMan2 = nullptr;
128 	_currentScene = nullptr;
129 	_loaderScene = nullptr;
130 	_scene2 = nullptr;
131 	_scene3 = nullptr;
132 	_messageHandlers = nullptr;
133 
134 	_updateScreenCallback = nullptr;
135 	_updateCursorCallback = nullptr;
136 
137 	_msgX = 0;
138 	_msgY = 0;
139 	_msgObjectId2 = 0;
140 	_msgId = 0;
141 	_mouseVirtX = 0;
142 	_mouseVirtY = 0;
143 
144 	_currSelectedInventoryItemId = 0;
145 
146 	_cursorId = 0;
147 
148 	_keyState = Common::KEYCODE_INVALID;
149 	_buttonState = 0;
150 
151 	_updateFlag = true;
152 	_flgCanOpenMap = true;
153 
154 	_sceneWidth = 1;
155 	_sceneHeight = 1;
156 
157 	_inventoryScene = nullptr;
158 	_inventory = nullptr;
159 
160 	_minCursorId = 0xffff;
161 	_maxCursorId = 0;
162 	_objectAtCursor = 0;
163 	_objectIdAtCursor = 0;
164 
165 	_arcadeOverlay = nullptr;
166 	_arcadeOverlayHelper = nullptr;
167 	_arcadeOverlayX = 0;
168 	_arcadeOverlayY = 0;
169 	_arcadeOverlayMidX = 0;
170 	_arcadeOverlayMidY = 0;
171 
172 	_isSaveAllowed = true;
173 
174 	g_nmi = this;
175 	g_vars = new Vars;
176 }
177 
~NGIEngine()178 NGIEngine::~NGIEngine() {
179 	delete g_vars;
180 	g_vars = nullptr;
181 }
182 
restartGame()183 void NGIEngine::restartGame() {
184 	_floaters->stopAll();
185 
186 	clearGlobalMessageQueueList();
187 	clearMessages();
188 
189 	initObjectStates();
190 
191 	if (_scene2) {
192 		_scene2->getAniMan();
193 		_scene2 = nullptr;
194 	}
195 
196 	if (_currentScene) {
197 		_gameLoader->unloadScene(_currentScene->_sceneId);
198 
199 		_currentScene = nullptr;
200 	}
201 
202 	_gameLoader->restoreDefPicAniInfos();
203 
204 	getGameLoaderInventory()->clear();
205 	getGameLoaderInventory()->addItem(ANI_INV_MAP, 1);
206 	getGameLoaderInventory()->rebuildItemRects();
207 
208 	initMap();
209 
210 	if (_flgPlayIntro) {
211 		_gameLoader->loadScene(SC_INTRO1);
212 		_gameLoader->gotoScene(SC_INTRO1, TrubaUp);
213 	} else {
214 		_gameLoader->loadScene(SC_1);
215 		_gameLoader->gotoScene(SC_1, TrubaLeft);
216 	}
217 }
218 
shouldQuit()219 bool NGIEngine::shouldQuit() {
220 	return !_gameContinue || Engine::shouldQuit();
221 }
222 
loadGameState(int slot)223 Common::Error NGIEngine::loadGameState(int slot) {
224 	deleteModalObject();
225 
226 	if (_gameLoader->readSavegame(getSavegameFile(slot)))
227 		return Common::kNoError;
228 	else
229 		return Common::kUnknownError;
230 }
231 
saveGameState(int slot,const Common::String & description,bool isAutosave)232 Common::Error NGIEngine::saveGameState(int slot, const Common::String &description, bool isAutosave) {
233 	if (_gameLoader->writeSavegame(_currentScene, getSavegameFile(slot), description))
234 		return Common::kNoError;
235 	else
236 		return Common::kUnknownError;
237 }
238 
getSaveStateName(int slot) const239 Common::String NGIEngine::getSaveStateName(int slot) const {
240 	return Common::String::format("%s.s%02d", getGameId(), slot);
241 }
242 
run()243 Common::Error NGIEngine::run() {
244 	const Graphics::PixelFormat format(4, 8, 8, 8, 8, 24, 16, 8, 0);
245 	// Initialize backend
246 	initGraphics(800, 600, &format);
247 
248 	_backgroundSurface.create(800, 600, format);
249 	_origFormat = Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0);
250 
251 	_globalMessageQueueList.reset(new GlobalMessageQueueList);
252 	_behaviorManager.reset(new BehaviorManager);
253 
254 	_sceneRect.left = 0;
255 	_sceneRect.top = 0;
256 	_sceneRect.right = 799;
257 	_sceneRect.bottom = 599;
258 
259 	_floaters.reset(new Floaters);
260 	_aniHandler.reset(new AniHandler);
261 	_globalPalette = &_defaultPalette;
262 
263 	_isSaveAllowed = false;
264 
265 	if (debugChannelSet(-1, kDebugXML))
266 		loadGameObjH();
267 
268 	int scene = 0;
269 	if (ConfMan.hasKey("boot_param"))
270 		scene = convertScene(ConfMan.getInt("boot_param"));
271 
272 	if (ConfMan.hasKey("save_slot"))
273 		scene = -1;
274 
275 	switch (getGameGID()) {
276 	case GID_FULLPIPE:
277 		if (!loadGam("fullpipe.gam", scene))
278 			return Common::kNoGameDataFoundError;
279 		break;
280 	case GID_MDREAM:
281 		if (!loadGam("new.gam", scene))
282 			return Common::kNoGameDataFoundError;
283 		break;
284 	default:
285 		warning("Unknown GID");
286 		return Common::kUnsupportedGameidError;
287 	}
288 
289 	if (ConfMan.hasKey("save_slot")) {
290 		loadGameState(ConfMan.getInt("save_slot"));
291 	}
292 
293 #if 0
294 	loadAllScenes();
295 #endif
296 
297 	int time1 = g_nmi->_system->getMillis();
298 
299 	// Center mouse
300 	_system->warpMouse(400, 300);
301 
302 	for (;;) {
303 		updateEvents();
304 		if (shouldQuit()) {
305 			break;
306 		}
307 
308 		int time2 = g_nmi->_system->getMillis();
309 
310 		// 30fps
311 		if (time2 - time1 >= 33 || !_normalSpeed) {
312 			time1 = time2;
313 			updateScreen();
314 		}
315 
316 		if (_needRestart) {
317 			delete _modalObject;
318 			freeGameLoader();
319 			_currentScene = nullptr;
320 			_updateTicks = 0;
321 			_globalPalette = &_defaultPalette;
322 
323 			loadGam("fullpipe.gam");
324 			_needRestart = false;
325 		}
326 
327 		_system->delayMillis(5);
328 		_system->updateScreen();
329 	}
330 
331 	delete _modalObject;
332 	freeGameLoader();
333 
334 	cleanup();
335 	_backgroundSurface.free();
336 
337 	return Common::kNoError;
338 }
339 
updateEvents()340 void NGIEngine::updateEvents() {
341 	Common::Event event;
342 	Common::EventManager *eventMan = _system->getEventManager();
343 	ExCommand *ex;
344 
345 	while (eventMan->pollEvent(event)) {
346 		switch (event.type) {
347 		case Common::EVENT_KEYDOWN:
348 			_keyState = event.kbd.keycode;
349 
350 			switch (event.kbd.keycode) {
351 			case Common::KEYCODE_SPACE:
352 				if (_gamePaused) {
353 					if (_modalObject) {
354 						if (_modalObject->init(42)) {
355 							_modalObject->update();
356 						} else {
357 							deleteModalObject();
358 						}
359 					} else {
360 						_gameLoader->updateSystems(42);
361 					}
362 					return;
363 				}
364 
365 				ex = new ExCommand(0, 17, 36, 0, 0, 0, 1, 0, 0, 0);
366 				ex->_param = 32;
367 				ex->_excFlags |= 3;
368 				ex->handle();
369 				break;
370 			case Common::KEYCODE_s:
371 				if (_gamePaused) {
372 					_gamePaused = 0;
373 					_flgGameIsRunning = true;
374 					return;
375 				}
376 
377 				ex = new ExCommand(0, 17, 36, 0, 0, 0, 1, 0, 0, 0);
378 				ex->_param = event.kbd.keycode;
379 				ex->_excFlags |= 3;
380 				ex->handle();
381 				break;
382 			case Common::KEYCODE_q:
383 				return;
384 				break;
385 			default:
386 				ex = new ExCommand(0, 17, 36, 0, 0, 0, 1, 0, 0, 0);
387 				ex->_param = event.kbd.keycode;
388 				ex->_excFlags |= 3;
389 				ex->handle();
390 				break;
391 			}
392 			break;
393 		case Common::EVENT_KEYUP:
394 			if (!_inputArFlag) {
395 				ex = new ExCommand(0, 17, 37, 0, 0, 0, 1, 0, 0, 0);
396 				ex->_excFlags |= 3;
397 				ex->handle();
398 			}
399 			_keyState = Common::KEYCODE_INVALID;
400 			break;
401 		case Common::EVENT_MOUSEMOVE:
402 			if (_recordEvents) {
403 				ex = new ExCommand(0, 17, 31, event.mouse.x, event.mouse.y, 0, 1, 0, 0, 0);
404 				ex->_excFlags |= 3;
405 				ex->handle();
406 			}
407 
408 			_mouseScreenPos = event.mouse;
409 			break;
410 		case Common::EVENT_QUIT:
411 			return;
412 		case Common::EVENT_RBUTTONDOWN:
413 			if (!_inputArFlag && (_updateTicks - _lastInputTicks) >= 2) {
414 				ex = new ExCommand(0, 17, 107, event.mouse.x, event.mouse.y, 0, 1, 0, 0, 0);
415 				ex->_excFlags |= 3;
416 				_lastInputTicks = _updateTicks;
417 				ex->handle();
418 			}
419 			_mouseScreenPos = event.mouse;
420 			break;
421 		case Common::EVENT_LBUTTONDOWN:
422 			if (!_inputArFlag && (_updateTicks - _lastInputTicks) >= 2) {
423 				ex = new ExCommand(0, 17, 29, event.mouse.x, event.mouse.y, 0, 1, 0, 0, 0);
424 
425 				ex->_sceneClickX = _sceneRect.left + ex->_x;
426 				ex->_sceneClickY = _sceneRect.top + ex->_y;
427 				ex->_param = getGameLoaderInventory()->getSelectedItemId();
428 				ex->_excFlags |= 3;
429 				_lastInputTicks = _updateTicks;
430 				ex->handle();
431 			}
432 			_mouseScreenPos = event.mouse;
433 			break;
434 		case Common::EVENT_LBUTTONUP:
435 			if (!_inputArFlag && (_updateTicks - _lastButtonUpTicks) >= 2) {
436 				ex = new ExCommand(0, 17, 30, 0, 0, 0, 1, 0, 0, 0);
437 				ex->_excFlags |= 3;
438 				_lastButtonUpTicks = _updateTicks;
439 				ex->handle();
440 			}
441 			_mouseScreenPos = event.mouse;
442 			break;
443 		default:
444 			break;
445 		}
446 	}
447 
448 	// pollEvent() is implemented only for video player. So skip it.
449 	//if (event.kbd.keycode == MSG_SC11_SHOWSWING && _modalObject) {
450 	//	_modalObject->pollEvent();
451 	//}
452 }
453 
freeGameLoader()454 void NGIEngine::freeGameLoader() {
455 	setCursor(0);
456 	_floaters->stopAll();
457 	_gameLoader.reset();
458 	_currentScene = 0;
459 	_scene2 = 0;
460 	_loaderScene = 0;
461 }
462 
cleanup()463 void NGIEngine::cleanup() {
464 	//cleanRecorder();
465 	clearMessageHandlers();
466 	clearMessages();
467 	_globalMessageQueueList->compact();
468 
469 	for (uint i = 0; i < _globalMessageQueueList->size(); i++)
470 		delete (*_globalMessageQueueList)[i];
471 
472 	stopAllSoundStreams();
473 }
474 
deleteModalObject()475 void NGIEngine::deleteModalObject() {
476 	if (!_modalObject)
477 		return;
478 
479 	_modalObject->saveload();
480 	BaseModalObject *tmp = _modalObject->_parentObj;
481 
482 	delete _modalObject;
483 
484 	_modalObject = tmp;
485 }
486 
updateScreen()487 void NGIEngine::updateScreen() {
488 	debugC(4, kDebugDrawing, "NGIEngine::updateScreen()");
489 
490 	_mouseVirtX = _mouseScreenPos.x + _sceneRect.left;
491 	_mouseVirtY = _mouseScreenPos.y + _sceneRect.top;
492 
493 	//if (inputArFlag)
494 	//	updateGame_inputArFlag();
495 
496 	if (_modalObject || (_flgGameIsRunning && (_gameLoader->updateSystems(42), _modalObject != 0))) {
497 		if (_flgGameIsRunning) {
498 			if (_modalObject->init(42)) {
499 				_modalObject->update();
500 			} else {
501 				deleteModalObject();
502 			}
503 		}
504 	} else if (_currentScene) {
505 		_currentScene->draw();
506 
507 		if (_inventoryScene)
508 			_inventory->draw();
509 
510 		if (_updateScreenCallback)
511 			_updateScreenCallback();
512 
513 		//if (inputArFlag && _currentScene) {
514 		//	vrtTextOut(*(_DWORD *)g_vrtHandle, smallNftData, "DEMO", 4, 380, 580);
515 		//	vrtTextOut(*(_DWORD *)g_vrtHandle, smallNftData, "Alt+F4 - exit", 14, 695, 580);
516 		//}
517 	} else {
518 		//vrtRectangle(*(_DWORD *)g_vrtHandle, 0, 0, 0, 800, 600);
519 	}
520 	_inputController->drawCursor(_mouseScreenPos.x, _mouseScreenPos.y);
521 
522 	++_updateTicks;
523 }
524 
getObjectEnumState(const Common::String & name,const char * state)525 int NGIEngine::getObjectEnumState(const Common::String &name, const char *state) {
526 	GameVar *var = _gameLoader->_gameVar->getSubVarByName("OBJSTATES");
527 
528 	if (!var) {
529 		var = _gameLoader->_gameVar->addSubVarAsInt("OBJSTATES", 0);
530 	}
531 
532 	var = var->getSubVarByName(name);
533 	if (var) {
534 		var = var->getSubVarByName("ENUMSTATES");
535 		if (var)
536 			return var->getSubVarAsInt(state);
537 	}
538 
539 	return 0;
540 }
541 
getObjectState(const Common::String & objname)542 int NGIEngine::getObjectState(const Common::String &objname) {
543 	GameVar *var = _gameLoader->_gameVar->getSubVarByName("OBJSTATES");
544 
545 	if (var)
546 		return var->getSubVarAsInt(objname);
547 
548   return 0;
549 }
550 
setObjectState(const Common::String & name,int state)551 void NGIEngine::setObjectState(const Common::String &name, int state) {
552 	GameVar *var = _gameLoader->_gameVar->getSubVarByName("OBJSTATES");
553 
554 	if (!var) {
555 		var = _gameLoader->_gameVar->addSubVarAsInt("OBJSTATES", 0);
556 	}
557 
558 	var->setSubVarAsInt(name, state);
559 }
560 
disableSaves(ExCommand * ex)561 void NGIEngine::disableSaves(ExCommand *ex) {
562 	if (_isSaveAllowed) {
563 		_isSaveAllowed = false;
564 
565 		if (_globalMessageQueueList->size() && (*_globalMessageQueueList)[0] != 0) {
566 			for (uint i = 0; i < _globalMessageQueueList->size(); i++) {
567 				if ((*_globalMessageQueueList)[i]->_flags & 1)
568 					if ((*_globalMessageQueueList)[i]->_id != ex->_parId && !(*_globalMessageQueueList)[i]->_isFinished)
569 						return;
570 			}
571 		}
572 
573 		// Original was makeing a save on every room entering
574 		if (_currentScene) {
575 			_gameLoader->saveScenePicAniInfos(_currentScene->_sceneId);
576 			//	_gameLoader->writeSavegame(_currentScene, "savetmp.sav");
577 		}
578 	}
579 }
580 
isSaveAllowed()581 bool NGIEngine::isSaveAllowed() {
582 	if (!g_nmi->_isSaveAllowed)
583 		return false;
584 
585 	bool allowed = true;
586 
587 	for (Common::Array<MessageQueue *>::iterator s = g_nmi->_globalMessageQueueList->begin(); s != g_nmi->_globalMessageQueueList->end(); ++s) {
588 		if (!(*s)->_isFinished && ((*s)->getFlags() & 1))
589 			allowed = false;
590 	}
591 
592 	return allowed;
593 }
594 
595 
596 } // End of namespace NGI
597